licensed 2.8.0 → 2.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/release.yml +11 -12
- data/.github/workflows/test.yml +71 -52
- data/.gitignore +2 -0
- data/CHANGELOG.md +48 -1
- data/README.md +11 -7
- data/docs/commands.md +8 -0
- data/docs/configuration.md +36 -0
- data/docs/sources/nuget.md +14 -0
- data/lib/licensed/cli.rb +7 -0
- data/lib/licensed/commands.rb +1 -0
- data/lib/licensed/commands/cache.rb +56 -19
- data/lib/licensed/commands/environment.rb +2 -2
- data/lib/licensed/commands/notices.rb +35 -0
- data/lib/licensed/commands/status.rb +5 -1
- data/lib/licensed/configuration.rb +72 -37
- data/lib/licensed/dependency.rb +1 -1
- data/lib/licensed/git.rb +3 -1
- data/lib/licensed/reporters.rb +1 -0
- data/lib/licensed/reporters/cache_reporter.rb +10 -10
- data/lib/licensed/reporters/list_reporter.rb +5 -5
- data/lib/licensed/reporters/notices_reporter.rb +68 -0
- data/lib/licensed/reporters/reporter.rb +3 -0
- data/lib/licensed/reporters/status_reporter.rb +7 -7
- data/lib/licensed/sources.rb +1 -0
- data/lib/licensed/sources/cabal.rb +2 -2
- data/lib/licensed/sources/go.rb +27 -20
- data/lib/licensed/sources/mix.rb +5 -1
- data/lib/licensed/sources/npm.rb +1 -0
- data/lib/licensed/sources/nuget.rb +212 -0
- data/lib/licensed/version.rb +1 -1
- data/licensed.gemspec +4 -3
- data/script/bootstrap +2 -1
- data/script/source-setup/nuget +17 -0
- metadata +37 -12
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -6,6 +6,53 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
6
6
|
|
7
7
|
## [Unreleased]
|
8
8
|
|
9
|
+
## 2.11.0
|
10
|
+
2020-06-02
|
11
|
+
|
12
|
+
### Added
|
13
|
+
- `notices` command to create a `NOTICE` file for each configured app (https://github.com/github/licensed/pull/277)
|
14
|
+
|
15
|
+
### Fixed
|
16
|
+
- NuGet source no longer crashes on a non-existent dependency path (https://github.com/github/licensed/pull/280)
|
17
|
+
- Go source no longer crashes on a non-existent dependency package path (https://github.com/github/licensed/pull/274)
|
18
|
+
|
19
|
+
## 2.10.0
|
20
|
+
2020-05-15
|
21
|
+
|
22
|
+
### Changed
|
23
|
+
- NPM source ignores missing peer dependencies (https://github.com/github/licensed/pull/267)
|
24
|
+
|
25
|
+
### Added
|
26
|
+
- NuGet source (:tada: @zarenner https://github.com/github/licensed/pull/261)
|
27
|
+
- Multiple apps can share a single cache location (https://github.com/github/licensed/pull/263)
|
28
|
+
|
29
|
+
## 2.9.2
|
30
|
+
2020-04-28
|
31
|
+
|
32
|
+
### Changed
|
33
|
+
- `licensee` minimum version bumped to 9.13.2 (https://github.com/github/licensed/pull/256)
|
34
|
+
|
35
|
+
## 2.9.1
|
36
|
+
2020-03-24
|
37
|
+
|
38
|
+
### Changed
|
39
|
+
- relaxed gem version restrictions on Thor (:tada: @eileencodes https://github.com/github/licensed/pull/254)
|
40
|
+
|
41
|
+
## 2.9.0
|
42
|
+
2020-03-19
|
43
|
+
|
44
|
+
### Added
|
45
|
+
- Source paths use glob pattern matching (https://github.com/github/licensed/pull/245)
|
46
|
+
|
47
|
+
### Fixed
|
48
|
+
- Mix source supports updates to mix.lock format (:tada: @bruce https://github.com/github/licensed/pull/242)
|
49
|
+
- Go source supports `go list` format changes in go 1.14 (https://github.com/github/licensed/pull/247)
|
50
|
+
|
51
|
+
### Changed
|
52
|
+
- `licensed cache` will flag dependencies for re-review when license text changes (https://github.com/github/licensed/pull/248)
|
53
|
+
- `licensed status` will raise errors on dependencies that need re-review (https://github.com/github/licensed/pull/248)
|
54
|
+
- `licensee` minimum version bumped to 9.13.1 (https://github.com/github/licensed/pull/251)
|
55
|
+
|
9
56
|
## 2.8.0
|
10
57
|
2020-01-03
|
11
58
|
|
@@ -265,4 +312,4 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
265
312
|
|
266
313
|
Initial release :tada:
|
267
314
|
|
268
|
-
[Unreleased]: https://github.com/github/licensed/compare/2.
|
315
|
+
[Unreleased]: https://github.com/github/licensed/compare/2.11.0...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.
|
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:
|
data/docs/commands.md
CHANGED
@@ -28,6 +28,14 @@ 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.
|
33
|
+
|
34
|
+
## `notices`
|
35
|
+
|
36
|
+
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`.
|
37
|
+
|
38
|
+
The `NOTICE` file contents are retrieved from cached records, with the assumption that cached records have already been reviewed in a compliance workflow.
|
31
39
|
|
32
40
|
## `env`
|
33
41
|
|
data/docs/configuration.md
CHANGED
@@ -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.
|
@@ -193,6 +209,26 @@ apps:
|
|
193
209
|
|
194
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`.
|
195
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
|
+
|
196
232
|
## Source specific configuration
|
197
233
|
|
198
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.
|
data/lib/licensed/cli.rb
CHANGED
@@ -28,6 +28,13 @@ module Licensed
|
|
28
28
|
run Licensed::Commands::List.new(config: config)
|
29
29
|
end
|
30
30
|
|
31
|
+
desc "notices", "Generate a NOTICE file from cached records"
|
32
|
+
method_option :config, aliases: "-c", type: :string,
|
33
|
+
desc: "Path to licensed configuration file"
|
34
|
+
def notices
|
35
|
+
run Licensed::Commands::Notices.new(config: config)
|
36
|
+
end
|
37
|
+
|
31
38
|
map "-v" => :version
|
32
39
|
map "--version" => :version
|
33
40
|
desc "version", "Show Installed Version of Licensed, [-v, --version]"
|
data/lib/licensed/commands.rb
CHANGED
@@ -11,20 +11,39 @@ 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
|
-
# Run the command for all
|
35
|
+
# Run the command for all enabled sources for an application configuration,
|
17
36
|
# recording results in a report.
|
18
|
-
# Removes any cached records that don't match a current application
|
19
|
-
# dependency.
|
20
37
|
#
|
21
|
-
# app -
|
22
|
-
# source - A dependency source enumerator
|
38
|
+
# app - An application configuration
|
23
39
|
#
|
24
|
-
# Returns whether the command succeeded for the
|
25
|
-
def
|
40
|
+
# Returns whether the command succeeded for the application.
|
41
|
+
def run_app(app)
|
26
42
|
result = super
|
27
|
-
|
43
|
+
|
44
|
+
# add the full cache path to the list of cache paths evaluted during this run
|
45
|
+
cache_paths << app.cache_path
|
46
|
+
|
28
47
|
result
|
29
48
|
end
|
30
49
|
|
@@ -45,8 +64,15 @@ module Licensed
|
|
45
64
|
filename = app.cache_path.join(source.class.type, "#{dependency.name}.#{DependencyRecord::EXTENSION}")
|
46
65
|
cached_record = Licensed::DependencyRecord.read(filename)
|
47
66
|
if options[:force] || save_dependency_record?(dependency, cached_record)
|
48
|
-
|
49
|
-
|
67
|
+
if dependency.record.matches?(cached_record)
|
68
|
+
# use the cached license value if the license text wasn't updated
|
69
|
+
dependency.record["license"] = cached_record["license"]
|
70
|
+
elsif cached_record && app.reviewed?(dependency.record)
|
71
|
+
# if the license text changed and the dependency is set as reviewed
|
72
|
+
# force a re-review of the dependency
|
73
|
+
dependency.record["review_changed_license"] = true
|
74
|
+
end
|
75
|
+
|
50
76
|
dependency.record.save(filename)
|
51
77
|
report["cached"] = true
|
52
78
|
end
|
@@ -55,6 +81,9 @@ module Licensed
|
|
55
81
|
report.warnings << "expected dependency path #{dependency.path} does not exist"
|
56
82
|
end
|
57
83
|
|
84
|
+
# add the absolute dependency file path to the list of files seen during this licensed run
|
85
|
+
files << filename.to_s
|
86
|
+
|
58
87
|
true
|
59
88
|
end
|
60
89
|
|
@@ -79,18 +108,26 @@ module Licensed
|
|
79
108
|
|
80
109
|
# Clean up cached files that dont match current dependencies
|
81
110
|
#
|
82
|
-
# app - An application configuration
|
83
|
-
# source - A dependency source enumerator
|
84
|
-
#
|
85
111
|
# Returns nothing
|
86
|
-
def clear_stale_cached_records
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
112
|
+
def clear_stale_cached_records
|
113
|
+
cache_paths.each do |cache_path|
|
114
|
+
Dir.glob(cache_path.join("**/*.#{DependencyRecord::EXTENSION}")).each do |file|
|
115
|
+
next if files.include?(file)
|
116
|
+
|
117
|
+
FileUtils.rm(file)
|
118
|
+
end
|
92
119
|
end
|
93
120
|
end
|
121
|
+
|
122
|
+
# Set of unique cache paths that are evaluted during the run
|
123
|
+
def cache_paths
|
124
|
+
@cache_paths ||= Set.new
|
125
|
+
end
|
126
|
+
|
127
|
+
# Set of unique absolute file paths of cached records evaluted during the run
|
128
|
+
def files
|
129
|
+
@files ||= Set.new
|
130
|
+
end
|
94
131
|
end
|
95
132
|
end
|
96
133
|
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
|
@@ -0,0 +1,35 @@
|
|
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
|
+
# Load stored dependency record data to add to the notices report.
|
17
|
+
#
|
18
|
+
# app - The application configuration for the dependency
|
19
|
+
# source - The dependency source enumerator for the dependency
|
20
|
+
# dependency - An application dependency
|
21
|
+
# report - A report hash for the command to provide extra data for the report output.
|
22
|
+
#
|
23
|
+
# Returns true.
|
24
|
+
def evaluate_dependency(app, source, dependency, report)
|
25
|
+
filename = app.cache_path.join(source.class.type, "#{dependency.name}.#{DependencyRecord::EXTENSION}")
|
26
|
+
report["cached_record"] = Licensed::DependencyRecord.read(filename)
|
27
|
+
if !report["cached_record"]
|
28
|
+
report["warning"] = "expected cached record not found at #{filename}"
|
29
|
+
end
|
30
|
+
|
31
|
+
true
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
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
|
-
|
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
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
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
|
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
|
-
#
|
30
|
-
|
31
|
-
|
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,20 @@ module Licensed
|
|
102
101
|
|
103
102
|
private
|
104
103
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
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
|
+
|
111
|
+
# if cache_path and shared_cache are both set in inherited_options,
|
112
|
+
# don't append the app name to the cache path
|
113
|
+
cache_path = inherited_options["cache_path"]
|
114
|
+
return cache_path if cache_path && inherited_options["shared_cache"] == true
|
115
|
+
|
116
|
+
cache_path ||= DEFAULT_CACHE_PATH
|
117
|
+
File.join(cache_path, self["name"])
|
112
118
|
end
|
113
119
|
|
114
120
|
def verify_arg(property)
|
@@ -118,9 +124,18 @@ module Licensed
|
|
118
124
|
end
|
119
125
|
end
|
120
126
|
|
121
|
-
class Configuration
|
127
|
+
class Configuration
|
128
|
+
DEFAULT_CONFIG_FILES = [
|
129
|
+
".licensed.yml".freeze,
|
130
|
+
".licensed.yaml".freeze,
|
131
|
+
".licensed.json".freeze
|
132
|
+
].freeze
|
133
|
+
|
122
134
|
class LoadError < StandardError; end
|
123
135
|
|
136
|
+
# An array of the applications in this licensed configuration.
|
137
|
+
attr_reader :apps
|
138
|
+
|
124
139
|
# Loads and returns a Licensed::Configuration object from the given path.
|
125
140
|
# The path can be relative or absolute, and can point at a file or directory.
|
126
141
|
# If the path given is a directory, the directory will be searched for a
|
@@ -133,21 +148,41 @@ module Licensed
|
|
133
148
|
|
134
149
|
def initialize(options = {})
|
135
150
|
apps = options.delete("apps") || []
|
136
|
-
|
137
|
-
|
138
|
-
|
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"]
|
151
|
+
apps << default_options.merge(options) if apps.empty?
|
152
|
+
apps = apps.flat_map { |app| self.class.expand_app_source_path(app) }
|
153
|
+
@apps = apps.map { |app| AppConfiguration.new(app, options) }
|
147
154
|
end
|
148
155
|
|
149
156
|
private
|
150
157
|
|
158
|
+
def self.expand_app_source_path(app_config)
|
159
|
+
return app_config if app_config["source_path"].to_s.empty?
|
160
|
+
|
161
|
+
source_path = File.expand_path(app_config["source_path"], AppConfiguration.root_for(app_config))
|
162
|
+
expanded_source_paths = Dir.glob(source_path).select { |p| File.directory?(p) }
|
163
|
+
# return the original configuration if glob didn't result in multiple paths
|
164
|
+
return app_config if expanded_source_paths.size <= 1
|
165
|
+
|
166
|
+
# map the expanded paths to new application configurations
|
167
|
+
expanded_source_paths.map do |path|
|
168
|
+
config = app_config.merge("source_path" => path)
|
169
|
+
|
170
|
+
# update configured values for name and cache_path for uniqueness.
|
171
|
+
# this is only needed when values are explicitly set, AppConfiguration
|
172
|
+
# will handle configurations that don't have these explicitly set
|
173
|
+
dir_name = File.basename(path)
|
174
|
+
config["name"] = "#{config["name"]}-#{dir_name}" if config["name"]
|
175
|
+
|
176
|
+
# if a cache_path is set and is not marked as shared, append the app name
|
177
|
+
# to the end of the cache path to make a unique cache path for the app
|
178
|
+
if config["cache_path"] && config["shared_cache"] != true
|
179
|
+
config["cache_path"] = File.join(config["cache_path"], dir_name)
|
180
|
+
end
|
181
|
+
|
182
|
+
config
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
151
186
|
# Find a default configuration file in the given directory.
|
152
187
|
# File preference is given by the order of elements in DEFAULT_CONFIG_FILES
|
153
188
|
#
|
@@ -198,7 +233,7 @@ module Licensed
|
|
198
233
|
# manually set a cache path without additional name
|
199
234
|
{
|
200
235
|
"source_path" => Dir.pwd,
|
201
|
-
"cache_path" => DEFAULT_CACHE_PATH
|
236
|
+
"cache_path" => AppConfiguration::DEFAULT_CACHE_PATH
|
202
237
|
}
|
203
238
|
end
|
204
239
|
end
|