licensed 3.0.1 → 3.2.2

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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.github/dependabot.yml +19 -0
  3. data/.github/workflows/release.yml +4 -4
  4. data/.github/workflows/test.yml +180 -47
  5. data/.ruby-version +1 -1
  6. data/CHANGELOG.md +60 -1
  7. data/README.md +25 -79
  8. data/docker/Dockerfile.build-linux +1 -1
  9. data/docs/adding_a_new_source.md +11 -8
  10. data/docs/commands/README.md +59 -0
  11. data/docs/commands/cache.md +35 -0
  12. data/docs/commands/env.md +10 -0
  13. data/docs/commands/list.md +23 -0
  14. data/docs/commands/migrate.md +10 -0
  15. data/docs/commands/notices.md +12 -0
  16. data/docs/commands/status.md +74 -0
  17. data/docs/commands/version.md +3 -0
  18. data/docs/configuration/README.md +11 -0
  19. data/docs/configuration/allowed_licenses.md +17 -0
  20. data/docs/configuration/application_name.md +63 -0
  21. data/docs/configuration/application_source.md +64 -0
  22. data/docs/configuration/configuration_root.md +27 -0
  23. data/docs/configuration/configuring_multiple_apps.md +58 -0
  24. data/docs/configuration/dependency_source_enumerators.md +28 -0
  25. data/docs/configuration/ignoring_dependencies.md +19 -0
  26. data/docs/configuration/metadata_cache.md +106 -0
  27. data/docs/configuration/reviewing_dependencies.md +18 -0
  28. data/docs/configuration.md +9 -161
  29. data/docs/sources/swift.md +4 -0
  30. data/lib/licensed/cli.rb +2 -2
  31. data/lib/licensed/commands/cache.rb +19 -20
  32. data/lib/licensed/commands/command.rb +104 -72
  33. data/lib/licensed/commands/environment.rb +12 -11
  34. data/lib/licensed/commands/list.rb +0 -19
  35. data/lib/licensed/commands/notices.rb +0 -19
  36. data/lib/licensed/commands/status.rb +13 -15
  37. data/lib/licensed/configuration.rb +105 -12
  38. data/lib/licensed/report.rb +44 -0
  39. data/lib/licensed/reporters/cache_reporter.rb +48 -64
  40. data/lib/licensed/reporters/json_reporter.rb +19 -21
  41. data/lib/licensed/reporters/list_reporter.rb +45 -58
  42. data/lib/licensed/reporters/notices_reporter.rb +33 -46
  43. data/lib/licensed/reporters/reporter.rb +37 -104
  44. data/lib/licensed/reporters/status_reporter.rb +58 -56
  45. data/lib/licensed/reporters/yaml_reporter.rb +19 -21
  46. data/lib/licensed/sources/bundler/definition.rb +36 -0
  47. data/lib/licensed/sources/bundler/missing_specification.rb +1 -1
  48. data/lib/licensed/sources/bundler.rb +38 -69
  49. data/lib/licensed/sources/dep.rb +2 -2
  50. data/lib/licensed/sources/go.rb +3 -3
  51. data/lib/licensed/sources/gradle.rb +2 -2
  52. data/lib/licensed/sources/helpers/content_versioning.rb +2 -1
  53. data/lib/licensed/sources/npm.rb +4 -3
  54. data/lib/licensed/sources/nuget.rb +56 -27
  55. data/lib/licensed/sources/swift.rb +69 -0
  56. data/lib/licensed/sources.rb +1 -0
  57. data/lib/licensed/version.rb +1 -1
  58. data/lib/licensed.rb +1 -0
  59. data/licensed.gemspec +4 -4
  60. data/script/source-setup/go +1 -1
  61. data/script/source-setup/swift +22 -0
  62. metadata +48 -13
  63. data/docs/commands.md +0 -95
@@ -3,80 +3,82 @@
3
3
  module Licensed
4
4
  module Reporters
5
5
  class StatusReporter < Reporter
6
- # Generate a report for a licensed status command run
7
- # Shows the errors found when checking status, as well as
8
- # overall number of dependencies checked
6
+ # Reports any errors encountered at the command level
9
7
  #
10
- # Returns the result of the yielded method
11
- def report_app(app)
12
- super do |report|
13
- shell.info "Checking cached dependency records for #{app["name"]}"
8
+ # command - The command being run
9
+ # report - A report object containing information about the command run
10
+ def end_report_command(command, report)
11
+ if report.errors.any?
12
+ shell.newline
13
+ report.errors.each { |e| shell.error e }
14
+ end
15
+ end
14
16
 
15
- result = yield report
17
+ # Reports the start of checking records for an app
18
+ #
19
+ # app - An application configuration
20
+ # report - A report containing information about the app evaluation
21
+ def begin_report_app(app, report)
22
+ shell.info "Checking cached dependency records for #{app["name"]}"
23
+ end
16
24
 
17
- all_reports = report.all_reports
25
+ # Reports any errors found when checking status, as well as
26
+ # overall number of dependencies checked
27
+ #
28
+ # app - An application configuration
29
+ # report - A report containing information about the app evaluation
30
+ def end_report_app(app, report)
31
+ all_reports = report.all_reports
18
32
 
19
- warning_reports = all_reports.select { |r| r.warnings.any? }.to_a
20
- if warning_reports.any?
21
- shell.newline
22
- shell.warn "Warnings:"
23
- warning_reports.each do |r|
24
- display_metadata = r.map { |k, v| "#{k}: #{v}" }.join(", ")
33
+ warning_reports = all_reports.select { |r| r.warnings.any? }.to_a
34
+ if warning_reports.any?
35
+ shell.newline
36
+ shell.warn "Warnings:"
37
+ warning_reports.each do |r|
38
+ display_metadata = r.map { |k, v| "#{k}: #{v}" }.join(", ")
25
39
 
26
- shell.warn "* #{r.name}"
27
- shell.warn " #{display_metadata}" unless display_metadata.empty?
28
- r.warnings.each do |warning|
29
- shell.warn " - #{warning}"
30
- end
31
- shell.newline
40
+ shell.warn "* #{r.name}"
41
+ shell.warn " #{display_metadata}" unless display_metadata.empty?
42
+ r.warnings.each do |warning|
43
+ shell.warn " - #{warning}"
32
44
  end
45
+ shell.newline
33
46
  end
47
+ end
34
48
 
35
- errored_reports = all_reports.select { |r| r.errors.any? }.to_a
49
+ errored_reports = all_reports.select { |r| r.errors.any? }.to_a
36
50
 
37
- dependency_count = all_reports.select { |r| r.target.is_a?(Licensed::Dependency) }.size
38
- error_count = errored_reports.sum { |r| r.errors.size }
51
+ dependency_count = all_reports.count { |r| r.target.is_a?(Licensed::Dependency) }
52
+ error_count = errored_reports.sum { |r| r.errors.size }
39
53
 
40
- if error_count > 0
41
- shell.newline
42
- shell.error "Errors:"
43
- errored_reports.each do |r|
44
- display_metadata = r.map { |k, v| "#{k}: #{v}" }.join(", ")
54
+ if error_count > 0
55
+ shell.newline
56
+ shell.error "Errors:"
57
+ errored_reports.each do |r|
58
+ display_metadata = r.map { |k, v| "#{k}: #{v}" }.join(", ")
45
59
 
46
- shell.error "* #{r.name}"
47
- shell.error " #{display_metadata}" unless display_metadata.empty?
48
- r.errors.each do |error|
49
- shell.error " - #{error}"
50
- end
51
- shell.newline
60
+ shell.error "* #{r.name}"
61
+ shell.error " #{display_metadata}" unless display_metadata.empty?
62
+ r.errors.each do |error|
63
+ shell.error " - #{error}"
52
64
  end
65
+ shell.newline
53
66
  end
54
-
55
- shell.newline
56
- shell.info "#{dependency_count} dependencies checked, #{error_count} errors found."
57
-
58
- result
59
67
  end
68
+
69
+ shell.newline
70
+ shell.info "#{dependency_count} dependencies checked, #{error_count} errors found."
60
71
  end
61
72
 
62
- # Reports on a dependency in a status command run.
63
- # Shows whether the dependency's status is valid in dot format
73
+ # Reports whether the dependency's status is valid in dot format
64
74
  #
65
75
  # dependency - An application dependency
66
- #
67
- # Returns the result of the yielded method
68
- # Note - must be called from inside the `report_run` scope
69
- def report_dependency(dependency)
70
- super do |report|
71
- result = yield report
72
-
73
- if report.errors.empty?
74
- shell.confirm(".", false)
75
- else
76
- shell.error("F", false)
77
- end
78
-
79
- result
76
+ # report - A report containing information about the dependency evaluation
77
+ def end_report_dependency(dependency, report)
78
+ if report.errors.empty?
79
+ shell.confirm(".", false)
80
+ else
81
+ shell.error("F", false)
80
82
  end
81
83
  end
82
84
  end
@@ -2,31 +2,29 @@
2
2
  module Licensed
3
3
  module Reporters
4
4
  class YamlReporter < Reporter
5
- def report_run(command)
6
- super do |report|
7
- result = yield report
8
-
9
- report["apps"] = report.reports.map(&:to_h) if report.reports.any?
10
- shell.info sanitize(report.to_h).to_yaml
11
-
12
- result
13
- end
5
+ # Report all information from the command run to the shell as a YAML object
6
+ #
7
+ # command - The command being run
8
+ # report - A report object containing information about the command run
9
+ def end_report_command(command, report)
10
+ report["apps"] = report.reports.map(&:to_h) if report.reports.any?
11
+ shell.info sanitize(report.to_h).to_yaml
14
12
  end
15
13
 
16
- def report_app(app)
17
- super do |report|
18
- result = yield report
19
- report["sources"] = report.reports.map(&:to_h) if report.reports.any?
20
- result
21
- end
14
+ # Add source report information to the app report hash
15
+ #
16
+ # app - An application configuration
17
+ # report - A report object containing information about the app evaluation
18
+ def end_report_app(app, report)
19
+ report["sources"] = report.reports.map(&:to_h) if report.reports.any?
22
20
  end
23
21
 
24
- def report_source(source)
25
- super do |report|
26
- result = yield report
27
- report["dependencies"] = report.reports.map(&:to_h) if report.reports.any?
28
- result
29
- end
22
+ # Add dependency report information to the source report hash
23
+ #
24
+ # source - A dependency source enumerator
25
+ # report - A report object containing information about the source evaluation
26
+ def end_report_source(source, report)
27
+ report["dependencies"] = report.reports.map(&:to_h) if report.reports.any?
30
28
  end
31
29
 
32
30
  def sanitize(object)
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Licensed
4
+ module Bundler
5
+ module DefinitionExtensions
6
+ attr_accessor :force_exclude_groups
7
+
8
+ # Override specs to avoid logic that would raise Gem::NotFound
9
+ # which is handled in this ./missing_specification.rb, and to not add
10
+ # bundler as a dependency if it's not a user-requested gem.
11
+ #
12
+ # Newer versions of Bundler have changed the implementation of specs_for
13
+ # as well which no longer calls this function. Overriding this function
14
+ # gives a stable access point for licensed
15
+ def specs
16
+ @specs ||= begin
17
+ specs = resolve.materialize(requested_dependencies)
18
+
19
+ all_dependencies = requested_dependencies.concat(specs.flat_map(&:dependencies))
20
+ if all_dependencies.any? { |d| d.name == "bundler" } && !specs["bundler"].any?
21
+ bundler = sources.metadata_source.specs.search(Gem::Dependency.new("bundler", ::Bundler::VERSION)).last
22
+ specs["bundler"] = bundler
23
+ end
24
+
25
+ specs
26
+ end
27
+ end
28
+
29
+ # Override requested_groups to also exclude any groups that are
30
+ # in the "bundler.without" section of the licensed configuration file.
31
+ def requested_groups
32
+ super - Array(force_exclude_groups)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -48,7 +48,7 @@ module Bundler
48
48
  spec = orig_materialize
49
49
  return spec if spec
50
50
 
51
- Licensed::Bundler:: MissingSpecification.new(name: name, version: version, platform: platform, source: source)
51
+ Licensed::Bundler::MissingSpecification.new(name: name, version: version, platform: platform, source: source)
52
52
  end
53
53
  end
54
54
  end
@@ -3,6 +3,7 @@ require "delegate"
3
3
  begin
4
4
  require "bundler"
5
5
  require "licensed/sources/bundler/missing_specification"
6
+ require "licensed/sources/bundler/definition"
6
7
  rescue LoadError
7
8
  end
8
9
 
@@ -29,7 +30,7 @@ module Licensed
29
30
  # `loaded_from` if available.
30
31
  def spec_file
31
32
  return @spec_file if defined?(@spec_file)
32
- return @spec_file = nil unless loaded_from && File.exist?(loaded_from)
33
+ return @spec_file = nil unless loaded_from && File.file?(loaded_from)
33
34
  @spec_file = begin
34
35
  file = { name: File.basename(loaded_from), dir: File.dirname(loaded_from) }
35
36
  Licensee::ProjectFiles::PackageManagerFile.new(File.read(loaded_from), file)
@@ -37,7 +38,6 @@ module Licensed
37
38
  end
38
39
  end
39
40
 
40
- GEMFILES = { "Gemfile" => "Gemfile.lock", "gems.rb" => "gems.locked" }
41
41
  DEFAULT_WITHOUT_GROUPS = %i{development test}
42
42
  RUBY_PACKER_ERROR = "The bundler source cannot be used from the executable built with ruby-packer. Please install licensed using `gem install` or using bundler."
43
43
 
@@ -45,15 +45,20 @@ module Licensed
45
45
  # running a ruby-packer-built licensed exe when ruby isn't available
46
46
  # could lead to errors if the host ruby doesn't exist
47
47
  return false if ruby_packer? && !Licensed::Shell.tool_available?("ruby")
48
- defined?(::Bundler) && lockfile_path && lockfile_path.exist?
48
+
49
+ # if Bundler isn't loaded, this enumerator won't work!
50
+ return false unless defined?(::Bundler)
51
+
52
+ with_application_environment { ::Bundler.default_lockfile&.exist? }
53
+ rescue ::Bundler::GemfileNotFound
54
+ false
49
55
  end
50
56
 
51
57
  def enumerate_dependencies
52
58
  raise Licensed::Sources::Source::Error.new(RUBY_PACKER_ERROR) if ruby_packer?
53
59
 
54
- with_local_configuration do
55
- specs.map do |spec|
56
- next if spec.name == "bundler" && !include_bundler?
60
+ with_application_environment do
61
+ definition.specs.map do |spec|
57
62
  next if spec.name == config["name"]
58
63
 
59
64
  error = spec.error if spec.respond_to?(:error)
@@ -73,41 +78,13 @@ module Licensed
73
78
  end
74
79
  end
75
80
 
76
- # Returns an array of Gem::Specifications for all gem dependencies
77
- def specs
78
- @specs ||= definition.specs_for(groups)
79
- end
80
-
81
- # Returns whether to include bundler as a listed dependency of the project
82
- def include_bundler?
83
- @include_bundler ||= begin
84
- # include if bundler is listed as a direct dependency that should be included
85
- requested_dependencies = definition.dependencies.select { |d| (d.groups & groups).any? && d.should_include? }
86
- return true if requested_dependencies.any? { |d| d.name == "bundler" }
87
- # include if bundler is an indirect dependency
88
- return true if specs.flat_map(&:dependencies).any? { |d| d.name == "bundler" }
89
- false
90
- end
91
- end
92
-
93
- # Build the bundler definition
94
81
  def definition
95
- @definition ||= ::Bundler::Definition.build(gemfile_path, lockfile_path, nil)
96
- end
97
-
98
- # Returns the bundle definition groups, removing "without" groups,
99
- # and including "with" groups
100
- def groups
101
- @groups ||= definition.groups - bundler_setting_array(:without) + bundler_setting_array(:with) - exclude_groups
102
- end
103
-
104
- # Returns a bundler setting as an array.
105
- # Depending on the version of bundler, array values are either returned as
106
- # a raw string ("a:b:c") or as an array ([:a, :b, :c])
107
- def bundler_setting_array(key)
108
- setting = ::Bundler.settings[key]
109
- setting = setting.split(":").map(&:to_sym) if setting.is_a?(String)
110
- Array(setting)
82
+ @definition ||= begin
83
+ definition = ::Bundler::Definition.build(::Bundler.default_gemfile, ::Bundler.default_lockfile, nil)
84
+ definition.extend Licensed::Bundler::DefinitionExtensions
85
+ definition.force_exclude_groups = exclude_groups
86
+ definition
87
+ end
111
88
  end
112
89
 
113
90
  # Returns any groups to exclude specified from both licensed configuration
@@ -121,41 +98,33 @@ module Licensed
121
98
  end
122
99
  end
123
100
 
124
- # Returns the path to the Bundler Gemfile
125
- def gemfile_path
126
- @gemfile_path ||= GEMFILES.keys
127
- .map { |g| config.pwd.join g }
128
- .find { |f| f.exist? }
129
- end
130
-
131
- # Returns the path to the Bundler Gemfile.lock
132
- def lockfile_path
133
- return unless gemfile_path
134
- @lockfile_path ||= gemfile_path.dirname.join(GEMFILES[gemfile_path.basename.to_s])
135
- end
136
-
137
- private
138
-
139
101
  # helper to clear all bundler environment around a yielded block
140
- def with_local_configuration
141
- # force bundler to use the local gem file
142
- original_bundle_gemfile, ENV["BUNDLE_GEMFILE"] = ENV["BUNDLE_GEMFILE"], gemfile_path.to_s
102
+ def with_application_environment
103
+ backup = nil
104
+
105
+ ::Bundler.ui.silence do
106
+ if ::Bundler.root != config.source_path
107
+ backup = ENV.to_hash
108
+ ENV.replace(::Bundler.original_env)
143
109
 
144
- # silence any bundler warnings while running licensed
145
- bundler_ui, ::Bundler.ui = ::Bundler.ui, ::Bundler::UI::Silent.new
110
+ # reset bundler to load from the current app's source path
111
+ ::Bundler.reset!
112
+ end
146
113
 
147
- # reset all bundler configuration
148
- ::Bundler.reset!
149
- # and re-configure with settings for current directory
150
- ::Bundler.configure
114
+ # ensure the bundler environment is loaded before enumeration
115
+ ::Bundler.load
151
116
 
152
- yield
117
+ yield
118
+ end
153
119
  ensure
154
- ENV["BUNDLE_GEMFILE"] = original_bundle_gemfile
155
- ::Bundler.ui = bundler_ui
120
+ if backup
121
+ # restore bundler configuration
122
+ ENV.replace(backup)
123
+ ::Bundler.reset!
124
+ end
156
125
 
157
- # restore bundler configuration
158
- ::Bundler.configure
126
+ # reload the bundler environment after enumeration
127
+ ::Bundler.load
159
128
  end
160
129
 
161
130
  # Returns whether the current licensed execution is running ruby-packer
@@ -40,10 +40,10 @@ module Licensed
40
40
  end
41
41
  end
42
42
 
43
- # Returns the godoc.org page for a package.
43
+ # Returns the pkg.go.dev page for a package.
44
44
  def homepage(import_path)
45
45
  return unless import_path
46
- "https://godoc.org/#{import_path}"
46
+ "https://pkg.go.dev/#{import_path}"
47
47
  end
48
48
 
49
49
  # Returns whether the package is part of the go std list. Replaces
@@ -98,7 +98,7 @@ module Licensed
98
98
  # Returns whether the package is local to the current project
99
99
  def local_package?(package)
100
100
  return false unless package && package["Dir"]
101
- return false unless File.fnmatch?("#{config.root.to_s}*", package["Dir"])
101
+ return false unless File.fnmatch?("#{config.root}*", package["Dir"], File::FNM_CASEFOLD)
102
102
  vendored_path_parts(package).nil?
103
103
  end
104
104
 
@@ -132,10 +132,10 @@ module Licensed
132
132
  end
133
133
  end
134
134
 
135
- # Returns the godoc.org page for a package.
135
+ # Returns the pkg.go.dev page for a package.
136
136
  def homepage(import_path)
137
137
  return unless import_path
138
- "https://godoc.org/#{import_path}"
138
+ "https://pkg.go.dev/#{import_path}"
139
139
  end
140
140
 
141
141
  # Returns the root directory to search for a package license
@@ -125,10 +125,10 @@ module Licensed
125
125
  def self.add_gradle_license_report_plugins_block(gradle_build_file)
126
126
 
127
127
  if gradle_build_file.include? "plugins"
128
- gradle_build_file.gsub(/(?<=plugins)\s+{/, " { id 'com.github.jk1.dependency-license-report' version '1.6'")
128
+ gradle_build_file.gsub(/(?<=plugins)\s+{/, " { id 'com.github.jk1.dependency-license-report' version '1.16'")
129
129
  else
130
130
 
131
- gradle_build_file = " plugins { id 'com.github.jk1.dependency-license-report' version '1.6' }" + gradle_build_file
131
+ gradle_build_file = " plugins { id 'com.github.jk1.dependency-license-report' version '1.16' }" + gradle_build_file
132
132
  end
133
133
  end
134
134
 
@@ -61,11 +61,12 @@ module Licensed
61
61
 
62
62
  paths = paths.compact.select { |path| File.file?(path) }
63
63
  return if paths.empty?
64
-
64
+ # rubocop:disable GitHub/InsecureHashAlgorithm
65
65
  paths.sort
66
66
  .reduce(Digest::XXHash64.new, :file)
67
67
  .digest
68
68
  .to_s(16) # convert to hex
69
+ # rubocop:enable GitHub/InsecureHashAlgorithm
69
70
  end
70
71
  end
71
72
  end