licensed 2.8.0 → 2.9.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2613c80ad97c8d19cea10560588a08406388e1860b21c18bfbd322843db428cc
4
- data.tar.gz: b34fc3f921feccd667898554166d69cb8d7b327e3756c30a3e418364d57cc4a6
3
+ metadata.gz: 64aab6dfce9359aa8e821d75416c11d276b67668d14b5db2c135301fd32fcbdd
4
+ data.tar.gz: 96862d7720c79ae99b7d500c5d15b0956a8a54bab54b811df4f12828427a8bc9
5
5
  SHA512:
6
- metadata.gz: 720754f1245f1043a2ff4721e0c2b7a403d020cb886277992d1d56a4c2145c588514fdcb18c3a1cd5aaf0d9409a5f839d73691ef0b167fbce69304c9aaf061ed
7
- data.tar.gz: f33e96346dbb63b48f78de856094992fa4bf58acd5ff811e9193e8e65e71308d91f4a4dc54bfa2b5b80bba1c3fc5348d94454bc00ddb7d012c20ecbed26ba441
6
+ metadata.gz: 2b57cd2414d204f2d547b3e1bcc64c026b7816a35afa370cdb315bdbdeccfb06b43b76abcc865cf468ea548b111c8bcce5396818537414732cba0a2256ca0525
7
+ data.tar.gz: 57f5618394751f20ff73f57209726400ec9197e1c21130cd8123864da7c4f7f3e868bf021d4fe6de0508a2328635178b696180d76e9aa7ff07d5ef6a49b22753
@@ -170,7 +170,7 @@ jobs:
170
170
  runs-on: ubuntu-latest
171
171
  strategy:
172
172
  matrix:
173
- go: [ '1.7.x', '1.10.x', '1.11.1' ]
173
+ go: [ '1.7.x', '1.10.x', '1.11.x', '1.12.x', '1.13.x', '1.14.x' ]
174
174
  steps:
175
175
  - uses: actions/checkout@master
176
176
  - name: Setup go
@@ -342,7 +342,7 @@ jobs:
342
342
  run: script/source-setup/pipenv
343
343
  - name: Run tests
344
344
  run: script/test pipenv
345
-
345
+
346
346
  yarn:
347
347
  runs-on: ubuntu-latest
348
348
  strategy:
@@ -6,6 +6,21 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## 2.9.0
10
+ 2020-03-19
11
+
12
+ ## Added
13
+ - Source paths use glob pattern matching (https://github.com/github/licensed/pull/245)
14
+
15
+ ## Fixed
16
+ - Mix source supports updates to mix.lock format (:tada: @bruce https://github.com/github/licensed/pull/242)
17
+ - Go source supports `go list` format changes in go 1.14 (https://github.com/github/licensed/pull/247)
18
+
19
+ ## Changed
20
+ - `licensed cache` will flag dependencies for re-review when license text changes (https://github.com/github/licensed/pull/248)
21
+ - `licensed status` will raise errors on dependencies that need re-review (https://github.com/github/licensed/pull/248)
22
+ - `licensee` minimum version bumped to 9.13.1 (https://github.com/github/licensed/pull/251)
23
+
9
24
  ## 2.8.0
10
25
  2020-01-03
11
26
 
@@ -265,4 +280,4 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
265
280
 
266
281
  Initial release :tada:
267
282
 
268
- [Unreleased]: https://github.com/github/licensed/compare/2.8.0...HEAD
283
+ [Unreleased]: https://github.com/github/licensed/compare/2.9.0...HEAD
@@ -28,6 +28,8 @@ A dependency will fail the status checks if:
28
28
  3. The cached record's `licenses` data is empty
29
29
  4. The cached record's `license` metadata doesn't match an `allowed` license from the dependency's application configuration.
30
30
  - If `license: other` is specified and all of the `licenses` entries match an `allowed` license a failure will not be logged
31
+ 5. The cached record is flagged for re-review.
32
+ - This occurs when the record's license text has changed since the record was reviewed.
31
33
 
32
34
  ## `env`
33
35
 
@@ -19,6 +19,22 @@ If a root path is not specified, it will default to using the following, in orde
19
19
  1. the root of the local git repository, if run inside a git repository
20
20
  2. the current directory
21
21
 
22
+ ### Source path glob patterns
23
+
24
+ The `source_path` property can use a glob path to share configuration properties across multiple application entrypoints.
25
+
26
+ For example, there is a common pattern in go projects to include multiple executable entrypoints under folders in `cmd`. Using a glob pattern allows users to avoid manually configuring and maintaining multiple licensed application `source_path`s. Using a glob pattern will also ensure that any new entrypoints matching the pattern are automatically picked up by licensed commands as they are added.
27
+
28
+ ```yml
29
+ sources:
30
+ go: true
31
+
32
+ # treat all directories under `cmd` as separate apps
33
+ source_path: cmd/*
34
+ ```
35
+
36
+ Glob patterns are syntactic sugar for, and provide the same functionality as, manually specifying multiple `source_path` values. See the instructions on [specifying multiple apps](./#specifying-multiple-apps) below for additional considerations when using multiple apps.
37
+
22
38
  ## Restricting sources
23
39
 
24
40
  The `sources` configuration property specifies which sources `licensed` will use to enumerate dependencies.
@@ -45,8 +45,15 @@ module Licensed
45
45
  filename = app.cache_path.join(source.class.type, "#{dependency.name}.#{DependencyRecord::EXTENSION}")
46
46
  cached_record = Licensed::DependencyRecord.read(filename)
47
47
  if options[:force] || save_dependency_record?(dependency, cached_record)
48
- # use the cached license value if the license text wasn't updated
49
- dependency.record["license"] = cached_record["license"] if dependency.record.matches?(cached_record)
48
+ if dependency.record.matches?(cached_record)
49
+ # use the cached license value if the license text wasn't updated
50
+ dependency.record["license"] = cached_record["license"]
51
+ elsif cached_record && app.reviewed?(dependency.record)
52
+ # if the license text changed and the dependency is set as reviewed
53
+ # force a re-review of the dependency
54
+ dependency.record["review_changed_license"] = true
55
+ end
56
+
50
57
  dependency.record.save(filename)
51
58
  report["cached"] = true
52
59
  end
@@ -23,14 +23,14 @@ module Licensed
23
23
  "allowed" => config["allowed"],
24
24
  "ignored" => config["ignored"],
25
25
  "reviewed" => config["reviewed"],
26
- "version_strategy" => self.version_strategy
26
+ "version_strategy" => self.version_strategy,
27
+ "root" => config.root
27
28
  }
28
29
  end
29
30
  end
30
31
 
31
32
  def run(**options)
32
33
  super do |report|
33
- report["root"] = config.root
34
34
  report["git_repo"] = Licensed::Git.git_repo?
35
35
  end
36
36
  end
@@ -35,7 +35,11 @@ module Licensed
35
35
  else
36
36
  report.errors << "cached dependency record out of date" if cached_record["version"] != dependency.version
37
37
  report.errors << "missing license text" if cached_record.licenses.empty?
38
- report.errors << "license needs review: #{cached_record["license"]}" if license_needs_review?(app, cached_record)
38
+ if cached_record["review_changed_license"]
39
+ report.errors << "license text has changed and needs re-review. if the new text is ok, remove the `review_changed_license` flag from the cached record"
40
+ elsif license_needs_review?(app, cached_record)
41
+ report.errors << "license needs review: #{cached_record["license"]}"
42
+ end
39
43
  end
40
44
 
41
45
  report.errors.empty?
@@ -4,40 +4,39 @@ require "pathname"
4
4
  module Licensed
5
5
  class AppConfiguration < Hash
6
6
  DEFAULT_CACHE_PATH = ".licenses".freeze
7
- DEFAULT_CONFIG_FILES = [
8
- ".licensed.yml".freeze,
9
- ".licensed.yaml".freeze,
10
- ".licensed.json".freeze
11
- ].freeze
7
+
8
+ # Returns the root for a configuration in following order of precendence:
9
+ # 1. explicitly configured "root" property
10
+ # 2. a found git repository root
11
+ # 3. the current directory
12
+ def self.root_for(configuration)
13
+ configuration["root"] || Licensed::Git.repository_root || Dir.pwd
14
+ end
12
15
 
13
16
  def initialize(options = {}, inherited_options = {})
14
17
  super()
15
18
 
16
19
  # update order:
17
20
  # 1. anything inherited from root config
18
- # 2. app defaults
19
- # 3. explicitly configured app settings
21
+ # 2. explicitly configured app settings
20
22
  update(inherited_options)
21
- update(defaults_for(options, inherited_options))
22
23
  update(options)
24
+ verify_arg "source_path"
23
25
 
24
26
  self["sources"] ||= {}
25
27
  self["reviewed"] ||= {}
26
28
  self["ignored"] ||= {}
27
29
  self["allowed"] ||= []
28
-
29
- # default the root to the git repository root,
30
- # or the current directory if no other options are available
31
- self["root"] ||= Licensed::Git.repository_root || Dir.pwd
32
-
33
- verify_arg "source_path"
34
- verify_arg "cache_path"
30
+ self["root"] = AppConfiguration.root_for(self)
31
+ # defaults to the directory name of the source path if not set
32
+ self["name"] ||= File.basename(self["source_path"])
33
+ # setting the cache path might need a valid app name
34
+ self["cache_path"] = detect_cache_path(options, inherited_options)
35
35
  end
36
36
 
37
37
  # Returns the path to the workspace root as a Pathname.
38
- # Defaults to Licensed::Git.repository_root if not explicitly set
39
38
  def root
40
- Pathname.new(self["root"])
39
+ @root ||= Pathname.new(self["root"])
41
40
  end
42
41
 
43
42
  # Returns the path to the app cache directory as a Pathname
@@ -102,13 +101,15 @@ module Licensed
102
101
 
103
102
  private
104
103
 
105
- def defaults_for(options, inherited_options)
106
- name = options["name"] || File.basename(options["source_path"])
104
+ # Returns the cache path for the application based on:
105
+ # 1. An explicitly set cache path for the application, if set
106
+ # 2. An inherited root cache path joined with the app name
107
+ # 3. The default cache path joined with the app name
108
+ def detect_cache_path(options, inherited_options)
109
+ return options["cache_path"] unless options["cache_path"].to_s.empty?
110
+
107
111
  cache_path = inherited_options["cache_path"] || DEFAULT_CACHE_PATH
108
- {
109
- "name" => name,
110
- "cache_path" => File.join(cache_path, name)
111
- }
112
+ File.join(cache_path, self["name"])
112
113
  end
113
114
 
114
115
  def verify_arg(property)
@@ -118,9 +119,18 @@ module Licensed
118
119
  end
119
120
  end
120
121
 
121
- class Configuration < AppConfiguration
122
+ class Configuration
123
+ DEFAULT_CONFIG_FILES = [
124
+ ".licensed.yml".freeze,
125
+ ".licensed.yaml".freeze,
126
+ ".licensed.json".freeze
127
+ ].freeze
128
+
122
129
  class LoadError < StandardError; end
123
130
 
131
+ # An array of the applications in this licensed configuration.
132
+ attr_reader :apps
133
+
124
134
  # Loads and returns a Licensed::Configuration object from the given path.
125
135
  # The path can be relative or absolute, and can point at a file or directory.
126
136
  # If the path given is a directory, the directory will be searched for a
@@ -133,21 +143,36 @@ module Licensed
133
143
 
134
144
  def initialize(options = {})
135
145
  apps = options.delete("apps") || []
136
- super(default_options.merge(options))
137
-
138
- self["apps"] = apps.map { |app| AppConfiguration.new(app, options) }
139
- end
140
-
141
- # Returns an array of the applications for this licensed configuration.
142
- # If the configuration did not explicitly configure any applications,
143
- # return self as an application configuration.
144
- def apps
145
- return [self] if self["apps"].empty?
146
- self["apps"]
146
+ apps << default_options.merge(options) if apps.empty?
147
+ apps = apps.flat_map { |app| self.class.expand_app_source_path(app) }
148
+ @apps = apps.map { |app| AppConfiguration.new(app, options) }
147
149
  end
148
150
 
149
151
  private
150
152
 
153
+ def self.expand_app_source_path(app_config)
154
+ return app_config if app_config["source_path"].to_s.empty?
155
+
156
+ source_path = File.expand_path(app_config["source_path"], AppConfiguration.root_for(app_config))
157
+ expanded_source_paths = Dir.glob(source_path).select { |p| File.directory?(p) }
158
+ # return the original configuration if glob didn't result in multiple paths
159
+ return app_config if expanded_source_paths.size <= 1
160
+
161
+ # map the expanded paths to new application configurations
162
+ expanded_source_paths.map do |path|
163
+ config = app_config.merge("source_path" => path)
164
+
165
+ # update configured values for name and cache_path for uniqueness.
166
+ # this is only needed when values are explicitly set, AppConfiguration
167
+ # will handle configurations that don't have these explicitly set
168
+ dir_name = File.basename(path)
169
+ config["name"] = "#{config["name"]}-#{dir_name}" if config["name"]
170
+ config["cache_path"] = File.join(config["cache_path"], dir_name) if config["cache_path"]
171
+
172
+ config
173
+ end
174
+ end
175
+
151
176
  # Find a default configuration file in the given directory.
152
177
  # File preference is given by the order of elements in DEFAULT_CONFIG_FILES
153
178
  #
@@ -198,7 +223,7 @@ module Licensed
198
223
  # manually set a cache path without additional name
199
224
  {
200
225
  "source_path" => Dir.pwd,
201
- "cache_path" => DEFAULT_CACHE_PATH
226
+ "cache_path" => AppConfiguration::DEFAULT_CACHE_PATH
202
227
  }
203
228
  end
204
229
  end
@@ -11,7 +11,9 @@ module Licensed
11
11
  # or nil if not in a git repository.
12
12
  def repository_root
13
13
  return unless available?
14
- Licensed::Shell.execute("git", "rev-parse", "--show-toplevel", allow_failure: true)
14
+ root = Licensed::Shell.execute("git", "rev-parse", "--show-toplevel", allow_failure: true)
15
+ return nil if root.empty?
16
+ root
15
17
  end
16
18
 
17
19
  # Returns true if a git repository is found, false otherwise
@@ -15,7 +15,8 @@ module Licensed
15
15
  def enumerate_dependencies
16
16
  with_configured_gopath do
17
17
  packages.map do |package|
18
- import_path = non_vendored_import_path(package["ImportPath"])
18
+ import_path = non_vendored_path(package["ImportPath"], root_package["ImportPath"])
19
+ import_path ||= package["ImportPath"]
19
20
  error = package.dig("Error", "Err") if package["Error"]
20
21
 
21
22
  Dependency.new(
@@ -86,14 +87,17 @@ module Licensed
86
87
  # true if go standard packages includes the import path as given
87
88
  return true if go_std_packages.include?(import_path)
88
89
  return true if go_std_packages.include?("vendor/#{import_path}")
90
+ return true if go_std_packages.include?(import_path.sub("golang.org", "internal"))
89
91
 
90
92
  # additional checks are only for vendored dependencies - return false
91
93
  # if package isn't vendored
92
- return false unless vendored_path?(import_path)
94
+ non_vendored_import_path = non_vendored_path(import_path, root_package["ImportPath"])
95
+ return false unless non_vendored_import_path
93
96
 
94
97
  # return true if any of the go standard packages matches against
95
98
  # the non-vendored import path
96
- return true if go_std_packages.include?(non_vendored_import_path(import_path))
99
+ return true if go_std_packages.include?(non_vendored_import_path)
100
+ return true if go_std_packages.include?(non_vendored_import_path.sub("golang.org", "internal"))
97
101
 
98
102
  # modify the import path to look like the import path `go list` returns for vendored std packages
99
103
  vendor_path = import_path.sub("#{root_package["ImportPath"]}/", "")
@@ -104,7 +108,7 @@ module Licensed
104
108
  def local_package?(package)
105
109
  return false unless package && package["ImportPath"]
106
110
  import_path = package["ImportPath"]
107
- import_path.start_with?(root_package["ImportPath"]) && !vendored_path?(import_path)
111
+ import_path.start_with?(root_package["ImportPath"]) && !import_path.include?("vendor/")
108
112
  end
109
113
 
110
114
  # Returns the version for a given package
@@ -150,34 +154,37 @@ module Licensed
150
154
  return if package.nil?
151
155
 
152
156
  # search root choices:
153
- # 1. module directory if using go modules
157
+ # 1. module directory if using go modules and directory is available
154
158
  # 2. vendor folder if package is vendored
155
159
  # 3. package root value if available
156
160
  # 4. GOPATH if the package directory is under the gopath
157
161
  # 5. nil
158
- return package.dig("Module", "Dir") if package["Module"]
159
- return package["Dir"].match("^(.*/vendor)/.*$")[1] if vendored_path?(package["Dir"])
162
+ module_dir = package.dig("Module", "Dir")
163
+ return module_dir if module_dir
164
+ return package["Dir"].match("^(.*/vendor)/.*$")[1] if vendored_path?(package["Dir"], config.root)
160
165
  return package["Root"] if package["Root"]
161
166
  return gopath if package["Dir"]&.start_with?(gopath)
162
167
  nil
163
168
  end
164
169
 
165
- # Returns whether a package is vendored or not based on the package
166
- # import_path
170
+ # Returns whether a package is vendored or not based on a base path and
171
+ # whether the path contains a vendor component
167
172
  #
168
173
  # path - Package path to test
169
- def vendored_path?(path)
170
- return false if path.nil?
171
- path.start_with?(root_package["ImportPath"]) && path.include?("vendor/")
174
+ # base - The base path that the input must start with
175
+ def vendored_path?(path, base)
176
+ return false if path.nil? || base.nil?
177
+ path.start_with?(base.to_s) && path.include?("vendor/")
172
178
  end
173
179
 
174
- # Returns the import path parameter without the vendor component
180
+ # Returns the path parameter without the vendor component if one is found
175
181
  #
176
- # import_path - Package import path with vendor component
177
- def non_vendored_import_path(import_path)
178
- return unless import_path
179
- return import_path unless vendored_path?(import_path)
180
- import_path.split("vendor/")[1]
182
+ # path - Package path with vendor component
183
+ # base - The base path that the input must start with
184
+ def non_vendored_path(path, base)
185
+ return unless path
186
+ return unless vendored_path?(path, base)
187
+ path.split("vendor/")[1]
181
188
  end
182
189
 
183
190
  # Returns a hash of information about the package with a given import path
@@ -93,8 +93,12 @@ module Licensed
93
93
  :[a-zA-Z0-9_]+ # after an Elixir atom,
94
94
  ,\s* # and skipping a comma and any number of spaces,
95
95
  "(?<version>.*?)" # capture the contents of a double-quoted string as the version,
96
- .* # and later
96
+ .*?\],\s* # and later
97
97
  "(?<repo>.*?)" # capture the contents of a double-quoted string as the repo
98
+ (?:
99
+ ,\s* # a comma
100
+ "[a-f0-9]{64}" # a digest
101
+ )?
98
102
  \},?\s*\Z # right before the final closing brace.
99
103
  /x,
100
104
 
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  module Licensed
3
- VERSION = "2.8.0".freeze
3
+ VERSION = "2.9.0".freeze
4
4
 
5
5
  def self.previous_major_versions
6
6
  major_version = Gem::Version.new(Licensed::VERSION).segments.first
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
23
23
 
24
24
  spec.required_ruby_version = ">= 2.3.0"
25
25
 
26
- spec.add_dependency "licensee", "~> 9.10"
26
+ spec.add_dependency "licensee", ">= 9.13.1", "< 10.0.0"
27
27
  spec.add_dependency "thor", "~> 0.19"
28
28
  spec.add_dependency "pathname-common_prefix", "~> 0.0.1"
29
29
  spec.add_dependency "tomlrb", "~> 1.2"
@@ -31,7 +31,7 @@ Gem::Specification.new do |spec|
31
31
  spec.add_dependency "ruby-xxHash", "~> 0.4"
32
32
  spec.add_dependency "parallel", ">= 0.18.0"
33
33
 
34
- spec.add_development_dependency "rake", "~> 10.0"
34
+ spec.add_development_dependency "rake", ">= 12.3.3"
35
35
  spec.add_development_dependency "minitest", "~> 5.8"
36
36
  spec.add_development_dependency "mocha", "~> 1.0"
37
37
  spec.add_development_dependency "rubocop", "~> 0.49", "< 0.67"
@@ -2,4 +2,5 @@
2
2
  set -euo pipefail
3
3
  IFS=$'\n\t'
4
4
 
5
- bundle install --path vendor/gems
5
+ bundle config set path 'vendor/gems'
6
+ bundle install
metadata CHANGED
@@ -1,29 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: licensed
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.8.0
4
+ version: 2.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitHub
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-01-03 00:00:00.000000000 Z
11
+ date: 2020-03-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: licensee
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 9.13.1
20
+ - - "<"
18
21
  - !ruby/object:Gem::Version
19
- version: '9.10'
22
+ version: 10.0.0
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
- - - "~>"
27
+ - - ">="
25
28
  - !ruby/object:Gem::Version
26
- version: '9.10'
29
+ version: 9.13.1
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: 10.0.0
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: thor
29
35
  requirement: !ruby/object:Gem::Requirement
@@ -112,16 +118,16 @@ dependencies:
112
118
  name: rake
113
119
  requirement: !ruby/object:Gem::Requirement
114
120
  requirements:
115
- - - "~>"
121
+ - - ">="
116
122
  - !ruby/object:Gem::Version
117
- version: '10.0'
123
+ version: 12.3.3
118
124
  type: :development
119
125
  prerelease: false
120
126
  version_requirements: !ruby/object:Gem::Requirement
121
127
  requirements:
122
- - - "~>"
128
+ - - ">="
123
129
  - !ruby/object:Gem::Version
124
- version: '10.0'
130
+ version: 12.3.3
125
131
  - !ruby/object:Gem::Dependency
126
132
  name: minitest
127
133
  requirement: !ruby/object:Gem::Requirement