licensed 2.15.2 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +55 -11
  3. data/CHANGELOG.md +56 -1
  4. data/README.md +38 -81
  5. data/docs/adding_a_new_source.md +11 -8
  6. data/docs/commands/README.md +59 -0
  7. data/docs/commands/cache.md +35 -0
  8. data/docs/commands/env.md +10 -0
  9. data/docs/commands/list.md +23 -0
  10. data/docs/commands/migrate.md +10 -0
  11. data/docs/commands/notices.md +12 -0
  12. data/docs/commands/status.md +73 -0
  13. data/docs/commands/version.md +3 -0
  14. data/docs/configuration.md +9 -161
  15. data/docs/configuration/README.md +11 -0
  16. data/docs/configuration/allowed_licenses.md +17 -0
  17. data/docs/configuration/application_name.md +63 -0
  18. data/docs/configuration/application_source.md +64 -0
  19. data/docs/configuration/configuration_root.md +27 -0
  20. data/docs/configuration/configuring_multiple_apps.md +58 -0
  21. data/docs/configuration/dependency_source_enumerators.md +28 -0
  22. data/docs/configuration/ignoring_dependencies.md +19 -0
  23. data/docs/configuration/metadata_cache.md +106 -0
  24. data/docs/configuration/reviewing_dependencies.md +18 -0
  25. data/docs/{migrating_to_newer_versions.md → migrations/v2.md} +1 -1
  26. data/docs/migrations/v3.md +109 -0
  27. data/docs/sources/bundler.md +1 -11
  28. data/docs/sources/swift.md +4 -0
  29. data/lib/licensed.rb +1 -0
  30. data/lib/licensed/cli.rb +6 -3
  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.rb +1 -0
  47. data/lib/licensed/sources/bundler.rb +36 -217
  48. data/lib/licensed/sources/bundler/missing_specification.rb +54 -0
  49. data/lib/licensed/sources/go.rb +1 -1
  50. data/lib/licensed/sources/gradle.rb +2 -2
  51. data/lib/licensed/sources/npm.rb +4 -3
  52. data/lib/licensed/sources/nuget.rb +57 -27
  53. data/lib/licensed/sources/swift.rb +69 -0
  54. data/lib/licensed/version.rb +1 -1
  55. data/script/source-setup/go +1 -1
  56. data/script/source-setup/swift +22 -0
  57. metadata +27 -4
  58. data/docs/commands.md +0 -95
@@ -2,17 +2,7 @@
2
2
 
3
3
  The bundler source will detect dependencies `Gemfile` and `Gemfile.lock` files are found at an apps `source_path`. The source uses the `Bundler` API to enumerate dependencies from `Gemfile` and `Gemfile.lock`.
4
4
 
5
- ### Enumerating bundler dependencies when using the licensed executable
6
-
7
- **Note** this content only applies to running licensed from an executable. It does not apply when using licensed as a gem.
8
-
9
- _It is required that the ruby runtime is available when running the licensed executable._
10
-
11
- The licensed executable contains and runs a version of ruby. When using the Bundler APIs, a mismatch between the version of ruby built into the licensed executable and the version of licensed used during `bundle install` can occur. This mismatch can lead to licensed raising errors due to not finding dependencies.
12
-
13
- For example, if `bundle install` was run with ruby 2.5.0 then the bundler specification path would be `<bundle path>/ruby/2.5.0/specifications`. However, if the licensed executable contains ruby 2.4.0, then licensed will be looking for specifications at `<bundle path>/ruby/2.4.0/specifications`. That path may not exist, or it may contain invalid or stale content.
14
-
15
- To prevent confusion, licensed uses the local ruby runtime to determine the ruby version for local gems during `bundle install`. If bundler is also available, then the ruby command will be run from a `bundle exec` context.
5
+ **Note** The bundler source cannot be used when running the [packaged licensed executable](../packaging.md)
16
6
 
17
7
  ### Excluding gem groups
18
8
 
@@ -0,0 +1,4 @@
1
+ # Swift
2
+
3
+ The Swift source uses `swift package` subcommands
4
+ to enumerate dependencies and properties.
data/lib/licensed.rb CHANGED
@@ -6,6 +6,7 @@ require "licensed/dependency"
6
6
  require "licensed/git"
7
7
  require "licensed/sources"
8
8
  require "licensed/configuration"
9
+ require "licensed/report"
9
10
  require "licensed/reporters"
10
11
  require "licensed/commands"
11
12
  require "licensed/ui/shell"
data/lib/licensed/cli.rb CHANGED
@@ -20,12 +20,12 @@ module Licensed
20
20
  end
21
21
 
22
22
  desc "status", "Check status of dependencies' cached licenses"
23
- method_option :format, enum: ["yaml", "json"],
24
- desc: "Output format"
25
23
  method_option :config, aliases: "-c", type: :string,
26
24
  desc: "Path to licensed configuration file"
27
25
  method_option :sources, aliases: "-s", type: :array,
28
26
  desc: "Individual source(s) to evaluate. Must also be enabled via configuration."
27
+ method_option :format, aliases: "-f", enum: ["yaml", "json"],
28
+ desc: "Output format"
29
29
  def status
30
30
  run Licensed::Commands::Status.new(config: config), sources: options[:sources], reporter: options[:format]
31
31
  end
@@ -74,11 +74,14 @@ module Licensed
74
74
  method_option :from, aliases: "-f", type: :string, required: true,
75
75
  desc: "Licensed version to migrate from - #{Licensed.previous_major_versions.map { |major| "v#{major}" }.join(", ")}"
76
76
  def migrate
77
+ shell = Thor::Base.shell.new
77
78
  case options["from"]
78
79
  when "v1"
79
80
  Licensed::Migrations::V2.migrate(options["config"])
81
+ when "v2"
82
+ shell.say "No configuration or cached file migration needed."
83
+ shell.say "Please see the documentation at https://github.com/github/licensed/tree/master/docs/migrations/v3.md for details."
80
84
  else
81
- shell = Thor::Base.shell.new
82
85
  shell.say "Unrecognized option from=#{options["from"]}", :red
83
86
  CLI.command_help(shell, "migrate")
84
87
  exit 1
@@ -11,6 +11,8 @@ module Licensed
11
11
  Licensed::Reporters::CacheReporter.new
12
12
  end
13
13
 
14
+ protected
15
+
14
16
  # Run the command.
15
17
  # Removes any cached records that don't match a current application
16
18
  # dependency.
@@ -18,20 +20,15 @@ module Licensed
18
20
  # options - Options to run the command with
19
21
  #
20
22
  # Returns whether the command was a success
21
- def run(**options)
22
- begin
23
- result = super
23
+ def run_command(report)
24
+ super do |result|
24
25
  clear_stale_cached_records if result
25
-
26
- result
27
- ensure
28
- cache_paths.clear
29
- files.clear
30
26
  end
27
+ ensure
28
+ cache_paths.clear
29
+ files.clear
31
30
  end
32
31
 
33
- protected
34
-
35
32
  # Run the command for all enumerated dependencies found in a dependency source,
36
33
  # recording results in a report.
37
34
  # Enumerating dependencies in the source is skipped if a :sources option
@@ -41,17 +38,12 @@ module Licensed
41
38
  # source - A dependency source enumerator
42
39
  #
43
40
  # Returns whether the command succeeded for the dependency source enumerator
44
- def run_source(app, source)
45
- super do |report|
46
- if Array(options[:sources]).any? && !options[:sources].include?(source.class.type)
47
- report.warnings << "skipped source"
48
- next :skip
49
- end
41
+ def run_source(app, source, report)
42
+ # add the full cache path to the list of cache paths
43
+ # that should be cleaned up after the command run
44
+ cache_paths << app.cache_path.join(source.class.type)
50
45
 
51
- # add the full cache path to the list of cache paths
52
- # that should be cleaned up after the command run
53
- cache_paths << app.cache_path.join(source.class.type)
54
- end
46
+ super
55
47
  end
56
48
 
57
49
  # Cache dependency record data.
@@ -70,6 +62,12 @@ module Licensed
70
62
 
71
63
  filename = app.cache_path.join(source.class.type, "#{dependency.name}.#{DependencyRecord::EXTENSION}")
72
64
  cached_record = Licensed::DependencyRecord.read(filename)
65
+
66
+ report["cached"] = false
67
+ report["license"] = cached_record["license"] if cached_record
68
+ report["filename"] = filename.to_s
69
+ report["version"] = dependency.version
70
+
73
71
  if options[:force] || save_dependency_record?(dependency, cached_record)
74
72
  if dependency.record.matches?(cached_record)
75
73
  # use the cached license value if the license text wasn't updated
@@ -82,6 +80,7 @@ module Licensed
82
80
 
83
81
  dependency.record.save(filename)
84
82
  report["cached"] = true
83
+ report["license"] = dependency.record["license"]
85
84
  end
86
85
 
87
86
  if !dependency.exist?
@@ -18,23 +18,12 @@ module Licensed
18
18
  def run(**options)
19
19
  @options = options
20
20
  @reporter = create_reporter(options)
21
- begin
22
- result = reporter.report_run(self) do |report|
23
- # allow additional report data to be given by commands
24
- if block_given?
25
- next true if (yield report) == :skip
26
- end
27
-
28
- config.apps.sort_by { |app| app["name"] }
29
- .map { |app| run_app(app) }
30
- .all?
31
- end
32
- ensure
33
- @options = nil
34
- @reporter = nil
35
- end
36
21
 
37
- result
22
+ command_report = Licensed::Report.new(name: nil, target: self)
23
+ run_command(command_report)
24
+ ensure
25
+ @options = nil
26
+ @reporter = nil
38
27
  end
39
28
 
40
29
  # Creates a reporter to use during a command run
@@ -64,36 +53,65 @@ module Licensed
64
53
 
65
54
  protected
66
55
 
56
+ # Run the command for all application configurations
57
+ #
58
+ # report - A Licensed::Report object for this command
59
+ #
60
+ # Returns whether the command succeeded
61
+ def run_command(report)
62
+ reporter.begin_report_command(self, report)
63
+ apps = config.apps.sort_by { |app| app["name"] }
64
+ results = apps.map do |app|
65
+ app_report = Licensed::Report.new(name: app["name"], target: app)
66
+ report.reports << app_report
67
+ run_app(app, app_report)
68
+ end
69
+
70
+ result = results.all?
71
+
72
+ yield(result) if block_given?
73
+
74
+ result
75
+ ensure
76
+ reporter.end_report_command(self, report)
77
+ end
78
+
67
79
  # Run the command for all enabled sources for an application configuration,
68
80
  # recording results in a report.
69
81
  #
70
82
  # app - An application configuration
83
+ # report - A report object for this application
71
84
  #
72
85
  # Returns whether the command succeeded for the application.
73
- def run_app(app)
74
- reporter.report_app(app) do |report|
75
- # ensure the app source path exists before evaluation
76
- if !Dir.exist?(app.source_path)
77
- report.errors << "No such directory #{app.source_path}"
78
- next false
79
- end
86
+ def run_app(app, report)
87
+ reporter.begin_report_app(app, report)
80
88
 
81
- Dir.chdir app.source_path do
82
- begin
83
- # allow additional report data to be given by commands
84
- if block_given?
85
- next true if (yield report) == :skip
86
- end
87
-
88
- app.sources.select(&:enabled?)
89
- .sort_by { |source| source.class.type }
90
- .map { |source| run_source(app, source) }.all?
91
- rescue Licensed::Shell::Error => err
92
- report.errors << err.message
93
- false
94
- end
89
+ # ensure the app source path exists before evaluation
90
+ if !Dir.exist?(app.source_path)
91
+ report.errors << "No such directory #{app.source_path}"
92
+ return false
93
+ end
94
+
95
+ Dir.chdir app.source_path do
96
+ sources = app.sources.select(&:enabled?)
97
+ .sort_by { |source| source.class.type }
98
+ results = sources.map do |source|
99
+ source_report = Licensed::Report.new(name: [report.name, source.class.type].join("."), target: source)
100
+ report.reports << source_report
101
+ run_source(app, source, source_report)
95
102
  end
103
+
104
+ result = results.all?
105
+
106
+ yield(result) if block_given?
107
+
108
+ result
96
109
  end
110
+ rescue Licensed::Shell::Error => err
111
+ report.errors << err.message
112
+ false
113
+ ensure
114
+ reporter.end_report_app(app, report)
97
115
  end
98
116
 
99
117
  # Run the command for all enumerated dependencies found in a dependency source,
@@ -101,27 +119,37 @@ module Licensed
101
119
  #
102
120
  # app - The application configuration for the source
103
121
  # source - A dependency source enumerator
122
+ # report - A report object for this source
104
123
  #
105
124
  # Returns whether the command succeeded for the dependency source enumerator
106
- def run_source(app, source)
107
- reporter.report_source(source) do |report|
108
- begin
109
- # allow additional report data to be given by commands
110
- if block_given?
111
- next true if (yield report) == :skip
112
- end
113
-
114
- source.dependencies.sort_by { |dependency| dependency.name }
115
- .map { |dependency| run_dependency(app, source, dependency) }
116
- .all?
117
- rescue Licensed::Shell::Error => err
118
- report.errors << err.message
119
- false
120
- rescue Licensed::Sources::Source::Error => err
121
- report.errors << err.message
122
- false
123
- end
125
+ def run_source(app, source, report)
126
+ reporter.begin_report_source(source, report)
127
+
128
+ if !sources_overrides.empty? && !sources_overrides.include?(source.class.type)
129
+ report.warnings << "skipped source"
130
+ return true
124
131
  end
132
+
133
+ dependencies = source.dependencies.sort_by { |dependency| dependency.name }
134
+ results = dependencies.map do |dependency|
135
+ dependency_report = Licensed::Report.new(name: [report.name, dependency.name].join("."), target: dependency)
136
+ report.reports << dependency_report
137
+ run_dependency(app, source, dependency, dependency_report)
138
+ end
139
+
140
+ result = results.all?
141
+
142
+ yield(result) if block_given?
143
+
144
+ result
145
+ rescue Licensed::Shell::Error => err
146
+ report.errors << err.message
147
+ false
148
+ rescue Licensed::Sources::Source::Error => err
149
+ report.errors << err.message
150
+ false
151
+ ensure
152
+ reporter.end_report_source(source, report)
125
153
  end
126
154
 
127
155
  # Run the command for a dependency, evaluating the dependency and
@@ -131,27 +159,27 @@ module Licensed
131
159
  # app - The application configuration for the dependency
132
160
  # source - The dependency source enumerator for the dependency
133
161
  # dependency - An application dependency
162
+ # report - A report object for this dependency
134
163
  #
135
164
  # Returns whether the command succeeded for the dependency
136
- def run_dependency(app, source, dependency)
137
- reporter.report_dependency(dependency) do |report|
138
- if dependency.errors?
139
- report.errors.concat(dependency.errors)
140
- return false
141
- end
165
+ def run_dependency(app, source, dependency, report)
166
+ reporter.begin_report_dependency(dependency, report)
142
167
 
143
- begin
144
- # allow additional report data to be given by commands
145
- if block_given?
146
- next true if (yield report) == :skip
147
- end
148
-
149
- evaluate_dependency(app, source, dependency, report)
150
- rescue Licensed::DependencyRecord::Error, Licensed::Shell::Error => err
151
- report.errors << err.message
152
- false
153
- end
168
+ if dependency.errors?
169
+ report.errors.concat(dependency.errors)
170
+ return false
154
171
  end
172
+
173
+ result = evaluate_dependency(app, source, dependency, report)
174
+
175
+ yield(result) if block_given?
176
+
177
+ result
178
+ rescue Licensed::DependencyRecord::Error, Licensed::Shell::Error => err
179
+ report.errors << err.message
180
+ false
181
+ ensure
182
+ reporter.end_report_dependency(dependency, report)
155
183
  end
156
184
 
157
185
  # Evaluate a dependency for the command. Must be implemented by a command implementation.
@@ -165,6 +193,10 @@ module Licensed
165
193
  def evaluate_dependency(app, source, dependency, report)
166
194
  raise "`evaluate_dependency` must be implemented by a command"
167
195
  end
196
+
197
+ def sources_overrides
198
+ @sources_overrides = Array(options[:sources])
199
+ end
168
200
  end
169
201
  end
170
202
  end
@@ -29,12 +29,6 @@ module Licensed
29
29
  end
30
30
  end
31
31
 
32
- def run(**options)
33
- super do |report|
34
- report["git_repo"] = Licensed::Git.git_repo?
35
- end
36
- end
37
-
38
32
  # Returns the default reporter to use during the command run
39
33
  #
40
34
  # options - The options the command was run with
@@ -46,11 +40,18 @@ module Licensed
46
40
 
47
41
  protected
48
42
 
49
- def run_app(app)
50
- reporter.report_app(app) do |report|
51
- report.merge! AppEnvironment.new(app).to_h
52
- true
53
- end
43
+ def run_command(report)
44
+ report["git_repo"] = Licensed::Git.git_repo?
45
+ super
46
+ end
47
+
48
+ def run_app(app, report)
49
+ report.merge! AppEnvironment.new(app).to_h
50
+ super
51
+ end
52
+
53
+ def run_source(app, source, report)
54
+ true
54
55
  end
55
56
  end
56
57
  end
@@ -13,25 +13,6 @@ module Licensed
13
13
 
14
14
  protected
15
15
 
16
- # Run the command for all enumerated dependencies found in a dependency source,
17
- # recording results in a report.
18
- # Enumerating dependencies in the source is skipped if a :sources option
19
- # is provided and the evaluated `source.class.type` is not in the :sources values
20
- #
21
- # app - The application configuration for the source
22
- # source - A dependency source enumerator
23
- #
24
- # Returns whether the command succeeded for the dependency source enumerator
25
- def run_source(app, source)
26
- super do |report|
27
- next if Array(options[:sources]).empty?
28
- next if options[:sources].include?(source.class.type)
29
-
30
- report.warnings << "skipped source"
31
- :skip
32
- end
33
- end
34
-
35
16
  # Listing dependencies requires no extra work.
36
17
  #
37
18
  # app - The application configuration for the dependency
@@ -13,25 +13,6 @@ module Licensed
13
13
 
14
14
  protected
15
15
 
16
- # Run the command for all enumerated dependencies found in a dependency source,
17
- # recording results in a report.
18
- # Enumerating dependencies in the source is skipped if a :sources option
19
- # is provided and the evaluated `source.class.type` is not in the :sources values
20
- #
21
- # app - The application configuration for the source
22
- # source - A dependency source enumerator
23
- #
24
- # Returns whether the command succeeded for the dependency source enumerator
25
- def run_source(app, source)
26
- super do |report|
27
- next if Array(options[:sources]).empty?
28
- next if options[:sources].include?(source.class.type)
29
-
30
- report.warnings << "skipped source"
31
- :skip
32
- end
33
- end
34
-
35
16
  # Load stored dependency record data to add to the notices report.
36
17
  #
37
18
  # app - The application configuration for the dependency