licensed 2.9.1 → 2.12.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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