licensed 2.8.0 → 2.9.0

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