licensed 2.9.1 → 2.12.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.
@@ -6,6 +6,47 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## 2.12.0
10
+ 2020-06-19
11
+
12
+ ### Added
13
+ - `--sources` argument for cache, list, status and notices commands to filter running sources (https://github.com/github/licensed/pull/287)
14
+
15
+ ### Fixed
16
+ - `cache` command will not remove files outside of enabled source cache paths (https://github.com/github/licensed/pull/287)
17
+
18
+ ## 2.11.1
19
+ 2020-06-09
20
+
21
+ ### Fixed
22
+ - `notices` command properly reads cached dependency notices contents (https://github.com/github/licensed/pull/283)
23
+
24
+ ## 2.11.0
25
+ 2020-06-02
26
+
27
+ ### Added
28
+ - `notices` command to create a `NOTICE` file for each configured app (https://github.com/github/licensed/pull/277)
29
+
30
+ ### Fixed
31
+ - NuGet source no longer crashes on a non-existent dependency path (https://github.com/github/licensed/pull/280)
32
+ - Go source no longer crashes on a non-existent dependency package path (https://github.com/github/licensed/pull/274)
33
+
34
+ ## 2.10.0
35
+ 2020-05-15
36
+
37
+ ### Changed
38
+ - NPM source ignores missing peer dependencies (https://github.com/github/licensed/pull/267)
39
+
40
+ ### Added
41
+ - NuGet source (:tada: @zarenner https://github.com/github/licensed/pull/261)
42
+ - Multiple apps can share a single cache location (https://github.com/github/licensed/pull/263)
43
+
44
+ ## 2.9.2
45
+ 2020-04-28
46
+
47
+ ### Changed
48
+ - `licensee` minimum version bumped to 9.13.2 (https://github.com/github/licensed/pull/256)
49
+
9
50
  ## 2.9.1
10
51
  2020-03-24
11
52
 
@@ -286,4 +327,4 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
286
327
 
287
328
  Initial release :tada:
288
329
 
289
- [Unreleased]: https://github.com/github/licensed/compare/2.9.1...HEAD
330
+ [Unreleased]: https://github.com/github/licensed/compare/2.11.1...HEAD
data/README.md CHANGED
@@ -24,13 +24,13 @@ See the [migration documentation](./docs/migrating_to_newer_versions.md) for mor
24
24
  ### Dependencies
25
25
 
26
26
  Licensed uses the `libgit2` bindings for Ruby provided by `rugged`. `rugged` requires `cmake` and `pkg-config` which you may need to install before you can install Licensed.
27
-
27
+
28
28
  > Ubuntu
29
-
29
+
30
30
  sudo apt-get install cmake pkg-config
31
-
31
+
32
32
  > OS X
33
-
33
+
34
34
  brew install cmake pkg-config
35
35
 
36
36
  ### With a Gemfile
@@ -64,8 +64,10 @@ For system wide usage, install licensed to a location on `$PATH`, e.g. `/usr/loc
64
64
 
65
65
  - `licensed list`: Output enumerated dependencies only.
66
66
  - `licensed cache`: Cache licenses and metadata.
67
- - `licensed status`: Check status of dependencies' cached licenses. For example:
67
+ - `licensed status`: Check status of dependencies' cached licenses.
68
+ - `licensed notices`: Write a `NOTICE` file for each application configuration.
68
69
  - `licensed version`: Show current installed version of Licensed. Aliases: `-v|--version`
70
+ - `licensed env`: Output environment information from the licensed configuration.
69
71
 
70
72
  See the [commands documentation](./docs/commands.md) for additional documentation, or run `licensed -h` to see all of the current available commands.
71
73
 
@@ -102,14 +104,16 @@ Dependencies will be automatically detected for all of the following sources by
102
104
  1. [Bundler](./docs/sources/bundler.md)
103
105
  1. [Cabal](./docs/sources/cabal.md)
104
106
  1. [Composer](./docs/sources/composer.md)
107
+ 1. [Git Submodules (git_submodule)](./docs/sources/git_submodule.md)
105
108
  1. [Go](./docs/sources/go.md)
106
109
  1. [Go Dep (dep)](./docs/sources/dep.md)
110
+ 1. [Gradle](./docs/sources/gradle.md)
107
111
  1. [Manifest lists (manifests)](./docs/sources/manifests.md)
112
+ 1. [Mix](./docs/sources/mix.md)
108
113
  1. [NPM](./docs/sources/npm.md)
114
+ 1. [NuGet](./docs/sources/nuget.md)
109
115
  1. [Pip](./docs/sources/pip.md)
110
116
  1. [Pipenv](./docs/sources/pipenv.md)
111
- 1. [Git Submodules (git_submodule)](./docs/sources/git_submodule.md)
112
- 1. [Mix](./docs/sources/mix.md)
113
117
  1. [Yarn](./docs/sources/yarn.md)
114
118
 
115
119
  You can disable any of them in the configuration file:
@@ -6,10 +6,14 @@ Run `licensed -h` to see help content for running licensed commands.
6
6
 
7
7
  Running the list command finds the dependencies for all sources in all configured applications. No additional actions are taken on each dependency.
8
8
 
9
+ An optional `--sources` flag can be given to limit which dependency sources are run. This is a filter over sources that are enabled via the licensed configuration file and cannot be used to run licensed with a disabled source.
10
+
9
11
  ## `cache`
10
12
 
11
13
  The cache command finds all dependencies and ensures that each dependency has an up-to-date cached record.
12
14
 
15
+ An optional `--sources` flag can be given to limit which dependency sources are run. This is a filter over sources that are enabled via the licensed configuration file and cannot be used to run licensed with a disabled source.
16
+
13
17
  Dependency records will be saved if:
14
18
  1. The `force` option is set
15
19
  2. No cached record is found
@@ -22,6 +26,8 @@ After the cache command is run, any cached records that don't match up to a curr
22
26
 
23
27
  The status command finds all dependencies and checks whether each dependency has a valid cached record.
24
28
 
29
+ An optional `--sources` flag can be given to limit which dependency sources are run. This is a filter over sources that are enabled via the licensed configuration file and cannot be used to run licensed with a disabled source.
30
+
25
31
  A dependency will fail the status checks if:
26
32
  1. No cached record is found
27
33
  2. The cached record's version is different than the current dependency's version
@@ -31,6 +37,14 @@ A dependency will fail the status checks if:
31
37
  5. The cached record is flagged for re-review.
32
38
  - This occurs when the record's license text has changed since the record was reviewed.
33
39
 
40
+ ## `notices`
41
+
42
+ Outputs license and notice text for all dependencies in each app into a `NOTICE` file in the app's `cache_path`. If an app uses a shared cache path, the file name will contain the app name as well, e.g. `NOTICE.my_app`.
43
+
44
+ An optional `--sources` flag can be given to limit which dependency sources are run. This is a filter over sources that are enabled via the licensed configuration file and cannot be used to run licensed with a disabled source.
45
+
46
+ The `NOTICE` file contents are retrieved from cached records, with the assumption that cached records have already been reviewed in a compliance workflow.
47
+
34
48
  ## `env`
35
49
 
36
50
  Prints the runtime environment used by licensed after loading a configuration file. By default the output is in YAML format, but can be output in JSON using the `--json` flag.
@@ -209,6 +209,26 @@ apps:
209
209
 
210
210
  In this example, the root configuration will contain a default cache path of `.licenses`. `app1` will inherit this value and append it's name, resulting in a cache path of `.licenses/app1`.
211
211
 
212
+ ### Sharing caches between apps
213
+
214
+ Dependency caches can be shared between apps by setting the same cache path on each app.
215
+
216
+ ```yaml
217
+ apps:
218
+ - source_path: "path/to/app1"
219
+ cache_path: ".licenses/apps"
220
+ - source_path: "path/to/app2"
221
+ cache_path: ".licenses/apps"
222
+ ```
223
+
224
+ When using a source path with a glob pattern, the apps created from the glob pattern can share a dependency by setting an explicit cache path and setting `shared_cache` to true.
225
+
226
+ ```yaml
227
+ source_path: "path/to/apps/*"
228
+ cache_path: ".licenses/apps"
229
+ shared_cache: true
230
+ ```
231
+
212
232
  ## Source specific configuration
213
233
 
214
234
  See the [source documentation](./sources) for details on any source specific configuration.
@@ -0,0 +1,14 @@
1
+ # NuGet
2
+
3
+ The NuGet source will detect ProjectReference-style restored packages by inspecting `project.assets.json` files for dependencies. It requires that `dotnet restore` has already ran on the project.
4
+
5
+ The source currently expects that `source_path` is set to the `obj` directory containing the `project.assets.json`.
6
+ For example, if your project lives at `foo/foo.proj`, you likely want to set `source_path` to `foo/obj`.
7
+ If in MSBuild you have customized your `obj` paths (e.g. to live outside your source tree), you may need to set `source_path` to something different such as `../obj/foo`.
8
+
9
+ ### Search strategy
10
+ This source looks for licenses:
11
+ 1. Specified by SPDX expression via `<license type="expression">` in a package's `.nuspec` (via licensee)
12
+ 2. In license files such as `LICENSE.txt`, even if not specified in the `.nuspec` (via licensee)
13
+ 3. Specified by filepath via `<license type="file">` in a package's `.nuspec`, even if not a standard license filename.
14
+ 4. By downloading and inspecting the contents of `<licenseUrl>` in a package's `.nuspec`, if not found otherwise.
@@ -10,22 +10,41 @@ module Licensed
10
10
  desc: "Overwrite licenses even if version has not changed."
11
11
  method_option :config, aliases: "-c", type: :string,
12
12
  desc: "Path to licensed configuration file"
13
+ method_option :sources, aliases: "-s", type: :array,
14
+ desc: "Individual source(s) to evaluate. Must also be enabled via configuration."
13
15
  def cache
14
- run Licensed::Commands::Cache.new(config: config), force: options[:force]
16
+ run Licensed::Commands::Cache.new(config: config),
17
+ { force: options[:force], sources: options[:sources] }
15
18
  end
16
19
 
17
20
  desc "status", "Check status of dependencies' cached licenses"
18
21
  method_option :config, aliases: "-c", type: :string,
19
22
  desc: "Path to licensed configuration file"
23
+ method_option :sources, aliases: "-s", type: :array,
24
+ desc: "Individual source(s) to evaluate. Must also be enabled via configuration."
20
25
  def status
21
- run Licensed::Commands::Status.new(config: config)
26
+ run Licensed::Commands::Status.new(config: config),
27
+ { sources: options[:sources] }
22
28
  end
23
29
 
24
30
  desc "list", "List dependencies"
25
31
  method_option :config, aliases: "-c", type: :string,
26
32
  desc: "Path to licensed configuration file"
33
+ method_option :sources, aliases: "-s", type: :array,
34
+ desc: "Individual source(s) to evaluate. Must also be enabled via configuration."
27
35
  def list
28
- run Licensed::Commands::List.new(config: config)
36
+ run Licensed::Commands::List.new(config: config),
37
+ { sources: options[:sources] }
38
+ end
39
+
40
+ desc "notices", "Generate a NOTICE file from cached records"
41
+ method_option :config, aliases: "-c", type: :string,
42
+ desc: "Path to licensed configuration file"
43
+ method_option :sources, aliases: "-s", type: :array,
44
+ desc: "Individual source(s) to evaluate. Must also be enabled via configuration."
45
+ def notices
46
+ run Licensed::Commands::Notices.new(config: config),
47
+ { sources: options[:sources] }
29
48
  end
30
49
 
31
50
  map "-v" => :version
@@ -6,5 +6,6 @@ module Licensed
6
6
  require "licensed/commands/status"
7
7
  require "licensed/commands/list"
8
8
  require "licensed/commands/environment"
9
+ require "licensed/commands/notices"
9
10
  end
10
11
  end
@@ -11,21 +11,47 @@ module Licensed
11
11
  Licensed::Reporters::CacheReporter.new
12
12
  end
13
13
 
14
+ # Run the command.
15
+ # Removes any cached records that don't match a current application
16
+ # dependency.
17
+ #
18
+ # options - Options to run the command with
19
+ #
20
+ # Returns whether the command was a success
21
+ def run(**options)
22
+ begin
23
+ result = super
24
+ clear_stale_cached_records if result
25
+
26
+ result
27
+ ensure
28
+ cache_paths.clear
29
+ files.clear
30
+ end
31
+ end
32
+
14
33
  protected
15
34
 
16
35
  # Run the command for all enumerated dependencies found in a dependency source,
17
36
  # recording results in a report.
18
- # Removes any cached records that don't match a current application
19
- # dependency.
37
+ # Enumerating dependencies in the source is skipped if a :sources option
38
+ # is provided and the evaluated `source.class.type` is not in the :sources values
20
39
  #
21
40
  # app - The application configuration for the source
22
41
  # source - A dependency source enumerator
23
42
  #
24
43
  # Returns whether the command succeeded for the dependency source enumerator
25
44
  def run_source(app, source)
26
- result = super
27
- clear_stale_cached_records(app, source) if result
28
- result
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
50
+
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
29
55
  end
30
56
 
31
57
  # Cache dependency record data.
@@ -62,6 +88,9 @@ module Licensed
62
88
  report.warnings << "expected dependency path #{dependency.path} does not exist"
63
89
  end
64
90
 
91
+ # add the absolute dependency file path to the list of files seen during this licensed run
92
+ files << filename.to_s
93
+
65
94
  true
66
95
  end
67
96
 
@@ -86,18 +115,26 @@ module Licensed
86
115
 
87
116
  # Clean up cached files that dont match current dependencies
88
117
  #
89
- # app - An application configuration
90
- # source - A dependency source enumerator
91
- #
92
118
  # Returns nothing
93
- def clear_stale_cached_records(app, source)
94
- names = source.dependencies.map { |dependency| File.join(source.class.type, dependency.name) }
95
- Dir.glob(app.cache_path.join(source.class.type, "**/*.#{DependencyRecord::EXTENSION}")).each do |file|
96
- file_path = Pathname.new(file)
97
- relative_path = file_path.relative_path_from(app.cache_path).to_s
98
- FileUtils.rm(file) unless names.include?(relative_path.chomp(".#{DependencyRecord::EXTENSION}"))
119
+ def clear_stale_cached_records
120
+ cache_paths.each do |cache_path|
121
+ Dir.glob(cache_path.join("**/*.#{DependencyRecord::EXTENSION}")).each do |file|
122
+ next if files.include?(file)
123
+
124
+ FileUtils.rm(file)
125
+ end
99
126
  end
100
127
  end
128
+
129
+ # Set of unique cache paths that are evaluted during the run
130
+ def cache_paths
131
+ @cache_paths ||= Set.new
132
+ end
133
+
134
+ # Set of unique absolute file paths of cached records evaluted during the run
135
+ def files
136
+ @files ||= Set.new
137
+ end
101
138
  end
102
139
  end
103
140
  end
@@ -21,7 +21,9 @@ module Licensed
21
21
  begin
22
22
  result = reporter.report_run(self) do |report|
23
23
  # allow additional report data to be given by commands
24
- yield report if block_given?
24
+ if block_given?
25
+ next if (yield report) == :skip
26
+ end
25
27
 
26
28
  config.apps.sort_by { |app| app["name"] }
27
29
  .map { |app| run_app(app) }
@@ -57,7 +59,9 @@ module Licensed
57
59
  Dir.chdir app.source_path do
58
60
  begin
59
61
  # allow additional report data to be given by commands
60
- yield report if block_given?
62
+ if block_given?
63
+ next if (yield report) == :skip
64
+ end
61
65
 
62
66
  app.sources.select(&:enabled?)
63
67
  .sort_by { |source| source.class.type }
@@ -81,7 +85,9 @@ module Licensed
81
85
  reporter.report_source(source) do |report|
82
86
  begin
83
87
  # allow additional report data to be given by commands
84
- yield report if block_given?
88
+ if block_given?
89
+ next if (yield report) == :skip
90
+ end
85
91
 
86
92
  source.dependencies.sort_by { |dependency| dependency.name }
87
93
  .map { |dependency| run_dependency(app, source, dependency) }
@@ -114,7 +120,9 @@ module Licensed
114
120
 
115
121
  begin
116
122
  # allow additional report data to be given by commands
117
- yield report if block_given?
123
+ if block_given?
124
+ next if (yield report) == :skip
125
+ end
118
126
 
119
127
  evaluate_dependency(app, source, dependency, report)
120
128
  rescue Licensed::Shell::Error => err
@@ -13,6 +13,25 @@ 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
+
16
35
  # Listing dependencies requires no extra work.
17
36
  #
18
37
  # app - The application configuration for the dependency
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+ module Licensed
3
+ module Commands
4
+ class Notices < Command
5
+ # Create a reporter to use during a command run
6
+ #
7
+ # options - The options the command was run with
8
+ #
9
+ # Raises a Licensed::Reporters::CacheReporter
10
+ def create_reporter(options)
11
+ Licensed::Reporters::NoticesReporter.new
12
+ end
13
+
14
+ protected
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
+ # Load stored dependency record data to add to the notices report.
36
+ #
37
+ # app - The application configuration for the dependency
38
+ # source - The dependency source enumerator for the dependency
39
+ # dependency - An application dependency
40
+ # report - A report hash for the command to provide extra data for the report output.
41
+ #
42
+ # Returns true.
43
+ def evaluate_dependency(app, source, dependency, report)
44
+ filename = app.cache_path.join(source.class.type, "#{dependency.name}.#{DependencyRecord::EXTENSION}")
45
+ report["cached_record"] = Licensed::DependencyRecord.read(filename)
46
+ if !report["cached_record"]
47
+ report.warnings << "expected cached record not found at #{filename}"
48
+ end
49
+
50
+ true
51
+ end
52
+ end
53
+ end
54
+ end