licensed 2.15.0 → 3.1.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: 6e90a33d845fe81078014cc53dd61cc5044fc908df4fe65e3c3ce05e11884e3f
4
- data.tar.gz: b0c1f03b192d70ec84d27f6b614d7874f32238a98a606f44e777cd1ee2e436ce
3
+ metadata.gz: 9411b608edb8210d926f1c927bcaa65e396eac39dbf6300b946e842a33071a23
4
+ data.tar.gz: a4dd2527919e79c111107482233945f476df72d9f282431abf99f40a3516b221
5
5
  SHA512:
6
- metadata.gz: d57bca03f12516e4802c50f4ac5483966659debe879f43f54ddec8d17a6ea05e88048693288a67e73ee68992981d127205025a16eb7fec7ae165554c3ea52d79
7
- data.tar.gz: 74d3df3dbdd3f52f7c22d2ca006cb0893f8ec32873bab9b7d56998ad4c1ade63080f608498561834273c23a0e8b8e29fbad0b39a5942951c75b0b04350b8532a
6
+ metadata.gz: 07f9153972ac85375a1cb8273d9990052a8d13bc3918c0cd7697c7a0686ef31ed45045065b10091b569a53ad0f2494ae32f0cf2392ae937d242b2954af92c0f6
7
+ data.tar.gz: 90364cc7be14673627b0280b018498a0feffc962b1c48a89094b9503d5743362f99ee0c36d601478b1818b10ee48d5b1ef97bdd120d6fbc7299b55efa9c5278c
@@ -362,6 +362,33 @@ jobs:
362
362
  - name: Run tests
363
363
  run: script/test pipenv
364
364
 
365
+ swift:
366
+ runs-on: ubuntu-latest
367
+ strategy:
368
+ matrix:
369
+ swift: [ "5.4", "5.3" ]
370
+ steps:
371
+ - uses: actions/checkout@v2
372
+ - name: Setup Swift
373
+ uses: fwal/setup-swift@v1
374
+ with:
375
+ swift-version: ${{ matrix.swift }}
376
+ - name: Set up Ruby
377
+ uses: ruby/setup-ruby@v1
378
+ with:
379
+ ruby-version: 2.6
380
+ - run: bundle lock
381
+ - uses: actions/cache@v1
382
+ with:
383
+ path: vendor/gems
384
+ key: ${{ runner.os }}-gem-2.6.x-${{ hashFiles('**/Gemfile.lock') }}
385
+ - name: Bootstrap
386
+ run: script/bootstrap
387
+ - name: Set up fixtures
388
+ run: script/source-setup/swift
389
+ - name: Run tests
390
+ run: script/test swift
391
+
365
392
  yarn:
366
393
  runs-on: ubuntu-latest
367
394
  strategy:
data/CHANGELOG.md CHANGED
@@ -6,6 +6,58 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## 3.1.0
10
+
11
+ 2021-06-16
12
+
13
+ ### Added
14
+
15
+ - Licensed supports Swift/Swift package manager as a dependency source (:tada: @mattt https://github.com/github/licensed/pull/363)'
16
+
17
+ ### Changed
18
+
19
+ - The `source_path` configuration property accepts arrays of inclusion and exclusion glob patterns (https://github.com/github/licensed/pull/368)
20
+ - The Nuget source now uses configured fallback folders to find dependencies that are not in found in the project folder (https://github.com/github/licensed/pull/366)
21
+ - The Nuget source supports a configurable property for the path from the project source path to the project's `obj` folder (https://github.com/github/licensed/pull/365)
22
+
23
+ ### Fixed
24
+ - The Go source's checks for local packages will correctly find paths in case-insensitive file systems (https://github.com/github/licensed/pull/370)
25
+ - The Bundler source will no longer unnecessarily reset the local Bundler environment configuration (https://github.com/github/licensed/pull/372)
26
+
27
+ ## 3.0.1
28
+
29
+ 2021-05-17
30
+
31
+ ### Fixed
32
+
33
+ - The bundler source will correctly enumerate dependencies pulled with a `git:` directive (https://github.com/github/licensed/pull/360)
34
+
35
+ ## 3.0.0
36
+
37
+ 2021-04-27
38
+
39
+ **This is a major release and includes potentially breaking changes to bundler dependency enumeration.**
40
+
41
+ ### Changed
42
+
43
+ - The bundler source will return an error when run from an executable. Please install licensed as a gem to continue using the bundler source. Please see the [v3 migration document](./docs/migrations/v3.md) for full details and migration strategies.
44
+
45
+ ## 2.15.2
46
+
47
+ 2021-04-06
48
+
49
+ ### Fixed
50
+
51
+ - The pip source works with package names containing periods (:tada: @bcskda https://github.com/github/licensed/pull/350)
52
+
53
+ ## 2.15.1
54
+
55
+ 2021-03-29
56
+
57
+ ### Changed
58
+
59
+ - The npm source will ignore dependencies that are marked as both extraneous and missing (https://github.com/github/licensed/pull/347)
60
+
9
61
  ## 2.15.0
10
62
  2021-03-24
11
63
 
@@ -395,4 +447,4 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
395
447
 
396
448
  Initial release :tada:
397
449
 
398
- [Unreleased]: https://github.com/github/licensed/compare/2.15.0...HEAD
450
+ [Unreleased]: https://github.com/github/licensed/compare/3.1.0...HEAD
data/README.md CHANGED
@@ -12,12 +12,24 @@ Licensed is **not** a complete open source license compliance solution. Please u
12
12
 
13
13
  Licensed is in active development and currently used at GitHub. See the [open issues](https://github.com/github/licensed/issues) for a list of potential work.
14
14
 
15
+ ## Licensed v3
16
+
17
+ Licensed v3 includes a breaking change if both of the following are true:
18
+
19
+ 1. a project uses bundler to manage ruby dependencies
20
+ 2. a project uses the self-contained executable build of licensed
21
+
22
+ All other usages of licensed should not encounter any major changes migrating from the latest 2.x build to 3.0.
23
+
24
+ See [CHANGELOG.md](./CHANGELOG.md) for more details on what's changed.
25
+ See the [v3 migration documentation](./docs/migrations/v3.md) for more info on migrating to v3.
26
+
15
27
  ## Licensed v2
16
28
 
17
29
  Licensed v2 includes many internal changes intended to make licensed more extensible and easier to update in the future. While not too much has changed externally, v2 is incompatible with configuration files and cached records from previous versions. Fortunately, migrating is easy using the `licensed migrate` command.
18
30
 
19
31
  See [CHANGELOG.md](./CHANGELOG.md) for more details on what's changed.
20
- See the [migration documentation](./docs/migrating_to_newer_versions.md) for more info on migrating to v2, or run `licensed help migrate`.
32
+ See the [v2 migration documentation](./docs/migrations/v2.md) for more info on migrating to v2, or run `licensed help migrate`.
21
33
 
22
34
  ## Installation
23
35
 
@@ -82,7 +94,6 @@ The [bundler-licensed plugin](https://github.com/sergey-alekseev/bundler-license
82
94
  The [licensed-ci](https://github.com/marketplace/actions/licensed-ci) GitHub Action runs `licensed` as part of an opinionated CI workflow and can be configured to run on any GitHub Action event. See the linked actions for usage and details.
83
95
 
84
96
  The [setup-licensed](https://github.com/marketplace/actions/setup-github-licensed) GitHub Action installs `licensed` to the workflow environment. See the linked actions for usage and details.
85
- - This action is intended for projects that don't have a ruby installation setup. If your workflow has ruby setup please install `licensed` via `Gemfile` + `bundle install` or with `gem install`.
86
97
 
87
98
  ### Configuration
88
99
 
@@ -114,6 +125,7 @@ Dependencies will be automatically detected for all of the following sources by
114
125
  1. [NuGet](./docs/sources/nuget.md)
115
126
  1. [Pip](./docs/sources/pip.md)
116
127
  1. [Pipenv](./docs/sources/pipenv.md)
128
+ 1. [Swift](./docs/sources/swift.md)
117
129
  1. [Yarn](./docs/sources/yarn.md)
118
130
 
119
131
  You can disable any of them in the configuration file:
@@ -19,9 +19,13 @@ 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
22
+ ### Source paths
23
23
 
24
- The `source_path` property can use a glob path to share configuration properties across multiple application entrypoints.
24
+ A source path is the directory in which licensed should run to enumerate dependencies. This is often dependent on the project type, for example the bundler source should be run from the directory containing a `Gemfile` or `gems.rb` while the go source should be run from the directory containing an entrypoint function.
25
+
26
+ #### Using glob patterns
27
+
28
+ The `source_path` property can use one or more glob patterns to share configuration properties across multiple application entrypoints.
25
29
 
26
30
  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
31
 
@@ -33,6 +37,14 @@ sources:
33
37
  source_path: cmd/*
34
38
  ```
35
39
 
40
+ In order to better filter the results from glob patterns, the `source_path` property also accepts an array of inclusion and exclusion glob patterns similar to gitignore files. Inclusion patterns will add matching directory paths to resulting set of source paths, while exclusion patterns will remove matching directory paths.
41
+
42
+ ```yml
43
+ source_path:
44
+ - "projects/*" # include by default all directories under "projects"
45
+ - "!projects/*Test" # exclude all projects ending in "Test"
46
+ ```
47
+
36
48
  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
49
 
38
50
  ## Restricting sources
@@ -1,3 +1,3 @@
1
- # Migrating your licensed configuration and cached records to the latest version of licensed
1
+ # Migrating your licensed configuration and cached records to licensed v2
2
2
 
3
3
  Licensed v2+ ships with an additional executable, `licensed-migrator`, that can be used to update your licensed files to the format expected by the currently installed version. To run, execute `licensed migrate --from v1 -c <path to licensed configuration file>`, replacing `v1` with the major version of licensed to migrate from.
@@ -0,0 +1,109 @@
1
+ # Breaking changes to bundler dependency enumeration in v3
2
+
3
+ **NOTE** If you are migrating from a version earlier than v2, please first [migrate to v2](./v2.md) before continuing.
4
+
5
+ Licensed v3 includes a breaking change to bundler dependency enumeration when using the executable form of licensed. Bundler dependency enumeration will no longer work with the licensed executable as of 3.0.0.
6
+
7
+ **If your project does not use bundler, or if you already install the licensed gem, you are not affected by this breaking change.**
8
+
9
+ ## Migrating bundler enumeration for v3
10
+
11
+ When using licensed v3 with bundler dependencies, licensed must be installed from its [gem](https://rubygems.org/gems/licensed). This can be accomplished with `gem install`, or by adding licensed to a bundler gem file.
12
+
13
+ ### Usage in a GitHub Actions workflow
14
+
15
+ Using licensed to enumerate bundler dependencies in a GitHub Actions workflow will require ruby to be available in the actions VM environment. Ruby can be setup in an actions workflow using [ruby/setup-ruby](https://github.com/ruby/setup-ruby)(preferred) or [actions/setup-ruby](https://github.com/actions/setup-ruby)(deprecated).
16
+
17
+ If you are using licensed in a GitHub Actions workflow, [jonabc/setup-licensed](https://github.com/jonabc/setup-licensed) has been updated according to this breaking change. `setup-licensed` will install the licensed gem when ruby is available, or the licensed executable when ruby is not available. Alternatively, you can `gem install` licensed directly as an actions step.
18
+
19
+ This is an example workflow definition that runs [jonabc/licensed-ci](https://github.com/jonabc/licensed-ci)'s opinionated license compliance workflow in CI. It includes jobs that demonstrate installing licensed using
20
+ - `gem install`
21
+ - [jonabc/setup-licensed](https://github.com/jonabc/setup-licensed)
22
+ - installing when included in a bundler gem file
23
+
24
+ ```yml
25
+ name: Cache and verify dependency license metadata
26
+
27
+ on:
28
+ # run when PRs are opened, reopened or updated
29
+ pull_request:
30
+ types:
31
+ - opened
32
+ - reopened
33
+ - synchronize
34
+
35
+ # run on demand
36
+ workflow_dispatch:
37
+
38
+ jobs:
39
+ # install licensed with setup-licensed
40
+ licensed-ci-setup-licensed:
41
+ runs-on: ubuntu-latest
42
+
43
+ steps:
44
+ # checkout the repo
45
+ - uses: actions/checkout@v1
46
+
47
+ # install ruby
48
+ - uses: ruby/setup-ruby@v1
49
+ with:
50
+ ruby-version: "3.0"
51
+
52
+ # install licensed gem using setup-licensed
53
+ - uses: jonabc/setup-licensed@v1
54
+ with:
55
+ version: '3.x'
56
+
57
+ # install dependencies in CI environment
58
+ - run: bundle install
59
+
60
+ # run licensed-ci to cache any metadata changes and verify compliance
61
+ - uses: jonabc/licensed-ci@v1
62
+
63
+ # OR
64
+
65
+ # install licensed using gem install
66
+ licensed-ci-gem:
67
+ runs-on: ubuntu-latest
68
+
69
+ steps:
70
+ # checkout the repo
71
+ - uses: actions/checkout@v1
72
+
73
+ # install ruby and bundler
74
+ - uses: ruby/setup-ruby@v1
75
+ with:
76
+ ruby-version: "3.0"
77
+
78
+ # install licensed gem using setup-licensed
79
+ - run: gem install licensed -v '~> 3.0'
80
+
81
+ # install dependencies in CI environment
82
+ - run: bundle install
83
+
84
+ # run licensed-ci to cache any metadata changes and verify compliance
85
+ - uses: jonabc/licensed-ci@v1
86
+
87
+ # OR
88
+
89
+ # install licensed as part of bundle installation
90
+ licensed-ci-bundle:
91
+ runs-on: ubuntu-latest
92
+
93
+ steps:
94
+ # checkout the repo
95
+ - uses: actions/checkout@v1
96
+
97
+ # install ruby and bundler
98
+ - uses: ruby/setup-ruby@v1
99
+ with:
100
+ ruby-version: "3.0"
101
+
102
+ # install licensed and other dependencies in CI environment
103
+ - run: bundle install
104
+
105
+ # run licensed-ci to cache any metadata changes and verify compliance
106
+ - uses: jonabc/licensed-ci@v1
107
+ with:
108
+ command: 'bundle exec licensed' # run licensed within the bundler context
109
+ ```
@@ -2,17 +2,7 @@
2
2
 
3
3
  The bundler source will detect dependencies `Gemfile` and `Gemfile.lock` files are found at an apps `source_path`. The source uses the `Bundler` API to enumerate dependencies from `Gemfile` and `Gemfile.lock`.
4
4
 
5
- ### Enumerating bundler dependencies when using the licensed executable
6
-
7
- **Note** this content only applies to running licensed from an executable. It does not apply when using licensed as a gem.
8
-
9
- _It is required that the ruby runtime is available when running the licensed executable._
10
-
11
- The licensed executable contains and runs a version of ruby. When using the Bundler APIs, a mismatch between the version of ruby built into the licensed executable and the version of licensed used during `bundle install` can occur. This mismatch can lead to licensed raising errors due to not finding dependencies.
12
-
13
- For example, if `bundle install` was run with ruby 2.5.0 then the bundler specification path would be `<bundle path>/ruby/2.5.0/specifications`. However, if the licensed executable contains ruby 2.4.0, then licensed will be looking for specifications at `<bundle path>/ruby/2.4.0/specifications`. That path may not exist, or it may contain invalid or stale content.
14
-
15
- To prevent confusion, licensed uses the local ruby runtime to determine the ruby version for local gems during `bundle install`. If bundler is also available, then the ruby command will be run from a `bundle exec` context.
5
+ **Note** The bundler source cannot be used when running the [packaged licensed executable](../packaging.md)
16
6
 
17
7
  ### Excluding gem groups
18
8
 
@@ -0,0 +1,4 @@
1
+ # Swift
2
+
3
+ The Swift source uses `swift package` subcommands
4
+ to enumerate dependencies and properties.
data/lib/licensed/cli.rb CHANGED
@@ -74,11 +74,14 @@ module Licensed
74
74
  method_option :from, aliases: "-f", type: :string, required: true,
75
75
  desc: "Licensed version to migrate from - #{Licensed.previous_major_versions.map { |major| "v#{major}" }.join(", ")}"
76
76
  def migrate
77
+ shell = Thor::Base.shell.new
77
78
  case options["from"]
78
79
  when "v1"
79
80
  Licensed::Migrations::V2.migrate(options["config"])
81
+ when "v2"
82
+ shell.say "No configuration or cached file migration needed."
83
+ shell.say "Please see the documentation at https://github.com/github/licensed/tree/master/docs/migrations/v3.md for details."
80
84
  else
81
- shell = Thor::Base.shell.new
82
85
  shell.say "Unrecognized option from=#{options["from"]}", :red
83
86
  CLI.command_help(shell, "migrate")
84
87
  exit 1
@@ -158,18 +158,41 @@ module Licensed
158
158
  private
159
159
 
160
160
  def self.expand_app_source_path(app_config)
161
- return app_config if app_config["source_path"].to_s.empty?
161
+ # map a source_path configuration value to an array of non-empty values
162
+ source_path_array = Array(app_config["source_path"])
163
+ .reject { |path| path.to_s.empty? }
164
+ .compact
165
+ app_root = AppConfiguration.root_for(app_config)
166
+ return app_config.merge("source_path" => app_root) if source_path_array.empty?
162
167
 
163
168
  # check if the source path maps to an existing directory
164
- source_path = File.expand_path(app_config["source_path"], AppConfiguration.root_for(app_config))
165
- return app_config if Dir.exist?(source_path)
169
+ if source_path_array.length == 1
170
+ source_path = File.expand_path(source_path_array[0], app_root)
171
+ return app_config.merge("source_path" => source_path) if Dir.exist?(source_path)
172
+ end
166
173
 
167
174
  # try to expand the source path for glob patterns
168
- expanded_source_paths = Dir.glob(source_path).select { |p| File.directory?(p) }
175
+ expanded_source_paths = source_path_array.reduce(Set.new) do |matched_paths, pattern|
176
+ current_matched_paths = if pattern.start_with?("!")
177
+ # if the pattern is an exclusion, remove all matching files
178
+ # from the result
179
+ matched_paths - Dir.glob(pattern[1..-1])
180
+ else
181
+ # if the pattern is an inclusion, add all matching files
182
+ # to the result
183
+ matched_paths + Dir.glob(pattern)
184
+ end
185
+
186
+ current_matched_paths.select { |p| File.directory?(p) }
187
+ end
188
+
169
189
  configs = expanded_source_paths.map { |path| app_config.merge("source_path" => path) }
170
190
 
171
191
  # if no directories are found for the source path, return the original config
172
- return app_config if configs.size == 0
192
+ if configs.size == 0
193
+ app_config["source_path"] = app_root if app_config["source_path"].is_a?(Array)
194
+ return app_config
195
+ end
173
196
 
174
197
  # update configured values for name and cache_path for uniqueness.
175
198
  # this is only needed when values are explicitly set, AppConfiguration
@@ -14,6 +14,7 @@ module Licensed
14
14
  require "licensed/sources/nuget"
15
15
  require "licensed/sources/pip"
16
16
  require "licensed/sources/pipenv"
17
+ require "licensed/sources/swift"
17
18
  require "licensed/sources/gradle"
18
19
  require "licensed/sources/mix"
19
20
  require "licensed/sources/yarn"
@@ -2,50 +2,13 @@
2
2
  require "delegate"
3
3
  begin
4
4
  require "bundler"
5
+ require "licensed/sources/bundler/missing_specification"
5
6
  rescue LoadError
6
7
  end
7
8
 
8
9
  module Licensed
9
10
  module Sources
10
11
  class Bundler < Source
11
- class MissingSpecification < Gem::BasicSpecification
12
- attr_reader :name, :requirement
13
- alias_method :version, :requirement
14
- def initialize(name:, requirement:)
15
- @name = name
16
- @requirement = requirement
17
- end
18
-
19
- def dependencies
20
- []
21
- end
22
-
23
- def source
24
- nil
25
- end
26
-
27
- def platform; end
28
- def gem_dir; end
29
- def gems_dir
30
- Gem.dir
31
- end
32
- def summary; end
33
- def homepage; end
34
-
35
- def error
36
- "could not find #{name} (#{requirement}) in any sources"
37
- end
38
- end
39
-
40
- class BundlerSpecification < ::SimpleDelegator
41
- def gem_dir
42
- dir = super
43
- return dir if File.exist?(dir)
44
-
45
- File.join(Gem.dir, "gems", full_name)
46
- end
47
- end
48
-
49
12
  class Dependency < Licensed::Dependency
50
13
  attr_reader :loaded_from
51
14
 
@@ -76,6 +39,7 @@ module Licensed
76
39
 
77
40
  GEMFILES = { "Gemfile" => "Gemfile.lock", "gems.rb" => "gems.locked" }
78
41
  DEFAULT_WITHOUT_GROUPS = %i{development test}
42
+ RUBY_PACKER_ERROR = "The bundler source cannot be used from the executable built with ruby-packer. Please install licensed using `gem install` or using bundler."
79
43
 
80
44
  def enabled?
81
45
  # running a ruby-packer-built licensed exe when ruby isn't available
@@ -85,13 +49,18 @@ module Licensed
85
49
  end
86
50
 
87
51
  def enumerate_dependencies
52
+ raise Licensed::Sources::Source::Error.new(RUBY_PACKER_ERROR) if ruby_packer?
53
+
88
54
  with_local_configuration do
89
55
  specs.map do |spec|
56
+ next if spec.name == "bundler" && !include_bundler?
57
+ next if spec.name == config["name"]
58
+
90
59
  error = spec.error if spec.respond_to?(:error)
91
60
  Dependency.new(
92
61
  name: spec.name,
93
62
  version: spec.version.to_s,
94
- path: spec.gem_dir,
63
+ path: spec.full_gem_path,
95
64
  loaded_from: spec.loaded_from,
96
65
  errors: Array(error),
97
66
  metadata: {
@@ -106,136 +75,18 @@ module Licensed
106
75
 
107
76
  # Returns an array of Gem::Specifications for all gem dependencies
108
77
  def specs
109
- # get the specifications for all dependencies in a Gemfile
110
- root_dependencies = definition.dependencies.select { |d| include?(d, nil) }
111
- root_specs = specs_for_dependencies(root_dependencies, nil).compact
112
-
113
- # recursively find the remaining specifications
114
- all_specs = recursive_specs(root_specs)
115
-
116
- # delete any specifications loaded from a gemspec
117
- all_specs.delete_if { |s| s.source.is_a?(::Bundler::Source::Gemspec) }
118
- end
119
-
120
- # Recursively finds the dependencies for Gem specifications.
121
- # Returns a `Set` containing the package names for all dependencies
122
- def recursive_specs(specs, results = Set.new)
123
- return [] if specs.nil? || specs.empty?
124
-
125
- new_specs = Set.new(specs) - results.to_a
126
- return [] if new_specs.empty?
127
-
128
- results.merge new_specs
129
-
130
- dependency_specs = new_specs.flat_map { |s| specs_for_dependencies(s.dependencies, s.source) }
131
-
132
- return results if dependency_specs.empty?
133
-
134
- results.merge recursive_specs(dependency_specs, results)
135
- end
136
-
137
- # Returns the specs for dependencies that pass the checks in `include?`.
138
- # Returns a `MissingSpecification` if a gem specification isn't found.
139
- def specs_for_dependencies(dependencies, source)
140
- included_dependencies = dependencies.select { |d| include?(d, source) }
141
- included_dependencies.map do |dep|
142
- gem_spec(dep) || MissingSpecification.new(name: dep.name, requirement: dep.requirement)
143
- end
144
- end
145
-
146
- # Returns a Gem::Specification for the provided gem argument.
147
- def gem_spec(dependency)
148
- return unless dependency
149
-
150
- # find a specifiction from the resolved ::Bundler::Definition specs
151
- spec = definition.resolve.find { |s| s.satisfies?(dependency) }
152
-
153
- # a nil spec should be rare, generally only seen from bundler
154
- return matching_spec(dependency) || bundle_exec_gem_spec(dependency.name, dependency.requirement) if spec.nil?
155
-
156
- # try to find a non-lazy specification that matches `spec`
157
- # spec.source.specs gives access to specifications with more
158
- # information than spec itself, including platform-specific gems.
159
- # these objects should have all the information needed to detect license metadata
160
- source_spec = spec.source.specs.find { |s| s.name == spec.name && s.version == spec.version }
161
- return source_spec if source_spec
162
-
163
- # look for a specification at the bundler specs path
164
- spec_path = ::Bundler.specs_path.join("#{spec.full_name}.gemspec")
165
- return Gem::Specification.load(spec_path.to_s) if File.exist?(spec_path.to_s)
166
-
167
- # if the specification file doesn't exist, get the specification using
168
- # the bundler and gem CLI
169
- bundle_exec_gem_spec(dependency.name, dependency.requirement)
170
- end
171
-
172
- # Returns whether a dependency should be included in the final
173
- def include?(dependency, source)
174
- # ::Bundler::Dependency has an extra `should_include?`
175
- return false unless dependency.should_include? if dependency.respond_to?(:should_include?)
176
-
177
- # Don't return gems added from `add_development_dependency` in a gemspec
178
- # if the :development group is excluded
179
- gemspec_source = source.is_a?(::Bundler::Source::Gemspec)
180
- return false if dependency.type == :development && (!gemspec_source || exclude_development_dependencies?)
181
-
182
- # Gem::Dependency don't have groups - in our usage these objects always
183
- # come as child-dependencies and are never directly from a Gemfile.
184
- # We assume that all Gem::Dependencies are ok at this point
185
- return true if dependency.groups.nil?
186
-
187
- # check if the dependency is in any groups we're interested in
188
- (dependency.groups & groups).any?
189
- end
190
-
191
- # Returns whether development dependencies should be excluded
192
- def exclude_development_dependencies?
193
- @include_development ||= begin
194
- # check whether the development dependency group is explicitly removed
195
- # or added via bundler and licensed configurations
196
- groups = [:development] - Array(::Bundler.settings[:without]) + Array(::Bundler.settings[:with]) - exclude_groups
197
- !groups.include?(:development)
198
- end
199
- end
200
-
201
- # Load a gem specification from the YAML returned from `gem specification`
202
- # This is a last resort when licensed can't obtain a specification from other means
203
- def bundle_exec_gem_spec(name, requirement)
204
- # `gem` must be available to run `gem specification`
205
- return unless Licensed::Shell.tool_available?("gem")
206
-
207
- # use `gem specification` with a clean ENV and clean Gem.dir paths
208
- # to get gem specification at the right directory
209
- begin
210
- ::Bundler.with_original_env do
211
- ::Bundler.rubygems.clear_paths
212
- yaml = Licensed::Shell.execute(*ruby_command_args("gem", "specification", name, "-v", requirement.to_s))
213
- spec = Gem::Specification.from_yaml(yaml)
214
- # this is horrible, but it will cache the gem_dir using the clean env
215
- # so that it can be used outside of this block when running from
216
- # the ruby packer executable environment
217
- spec.gem_dir if ruby_packer?
218
- spec
219
- end
220
- rescue Licensed::Shell::Error
221
- # return nil
222
- ensure
223
- ::Bundler.configure
224
- end
78
+ @specs ||= definition.specs_for(groups)
225
79
  end
226
80
 
227
- # Loads a dependency specification using rubygems' built-in
228
- # `Dependency#matching_specs` and `Dependency#to_spec`, from the original
229
- # gem environment
230
- def matching_spec(dependency)
231
- begin
232
- ::Bundler.with_original_env do
233
- ::Bundler.rubygems.clear_paths
234
- return unless dependency.matching_specs(true).any?
235
- BundlerSpecification.new(dependency.to_spec)
236
- end
237
- ensure
238
- ::Bundler.configure
81
+ # Returns whether to include bundler as a listed dependency of the project
82
+ def include_bundler?
83
+ @include_bundler ||= begin
84
+ # include if bundler is listed as a direct dependency that should be included
85
+ requested_dependencies = definition.dependencies.select { |d| (d.groups & groups).any? && d.should_include? }
86
+ return true if requested_dependencies.any? { |d| d.name == "bundler" }
87
+ # include if bundler is an indirect dependency
88
+ return true if specs.flat_map(&:dependencies).any? { |d| d.name == "bundler" }
89
+ false
239
90
  end
240
91
  end
241
92
 
@@ -283,71 +134,39 @@ module Licensed
283
134
  @lockfile_path ||= gemfile_path.dirname.join(GEMFILES[gemfile_path.basename.to_s])
284
135
  end
285
136
 
286
- # Returns the configured bundler executable to use, or "bundle" by default.
287
- def bundler_exe
288
- @bundler_exe ||= begin
289
- exe = config.dig("bundler", "bundler_exe")
290
- return "bundle" unless exe
291
- return exe if Licensed::Shell.tool_available?(exe)
292
- config.root.join(exe)
293
- end
294
- end
295
-
296
- # Determines if the configured bundler executable is available and returns
297
- # shell command args with or without `bundle exec` depending on availability.
298
- def ruby_command_args(*args)
299
- return Array(args) unless Licensed::Shell.tool_available?(bundler_exe)
300
- [bundler_exe, "exec", *args]
301
- end
302
-
303
- private
304
-
305
137
  # helper to clear all bundler environment around a yielded block
306
138
  def with_local_configuration
307
- # force bundler to use the local gem file
308
- original_bundle_gemfile, ENV["BUNDLE_GEMFILE"] = ENV["BUNDLE_GEMFILE"], gemfile_path.to_s
139
+ # silence any bundler warnings while running licensed
140
+ bundler_ui, ::Bundler.ui = ::Bundler.ui, ::Bundler::UI::Silent.new
309
141
 
310
- if ruby_packer?
311
- # if running under ruby-packer, set environment from host
142
+ original_bundle_gemfile = nil
143
+ if gemfile_path.to_s != ENV["BUNDLE_GEMFILE"]
144
+ # force bundler to use the local gem file
145
+ original_bundle_gemfile, ENV["BUNDLE_GEMFILE"] = ENV["BUNDLE_GEMFILE"], gemfile_path.to_s
312
146
 
313
- # hack: setting this ENV var allows licensed to use Gem paths outside
314
- # of the ruby-packer filesystem. this is needed to find spec sources
315
- # from the host filesystem
316
- ENV["ENCLOSE_IO_RUBYC_1ST_PASS"] = "1"
317
- ruby_version = Gem::ConfigMap[:ruby_version]
318
- # set the ruby version in Gem::ConfigMap to the ruby version from the host.
319
- # this helps Bundler find the correct spec sources and paths
320
- Gem::ConfigMap[:ruby_version] = host_ruby_version
147
+ # reset all bundler configuration
148
+ ::Bundler.reset!
149
+ # and re-configure with settings for current directory
150
+ ::Bundler.configure
321
151
  end
322
152
 
323
- # reset all bundler configuration
324
- ::Bundler.reset!
325
- # and re-configure with settings for current directory
326
- ::Bundler.configure
327
-
328
153
  yield
329
154
  ensure
330
- if ruby_packer?
331
- # if running under ruby-packer, restore environment after block is finished
332
- ENV.delete("ENCLOSE_IO_RUBYC_1ST_PASS")
333
- Gem::ConfigMap[:ruby_version] = ruby_version
155
+ if original_bundle_gemfile
156
+ ENV["BUNDLE_GEMFILE"] = original_bundle_gemfile
157
+
158
+ # restore bundler configuration
159
+ ::Bundler.reset!
160
+ ::Bundler.configure
334
161
  end
335
162
 
336
- ENV["BUNDLE_GEMFILE"] = original_bundle_gemfile
337
- # restore bundler configuration
338
- ::Bundler.reset!
339
- ::Bundler.configure
163
+ ::Bundler.ui = bundler_ui
340
164
  end
341
165
 
342
166
  # Returns whether the current licensed execution is running ruby-packer
343
167
  def ruby_packer?
344
168
  @ruby_packer ||= RbConfig::TOPDIR =~ /__enclose_io_memfs__/
345
169
  end
346
-
347
- # Returns the ruby version found in the bundler environment
348
- def host_ruby_version
349
- Licensed::Shell.execute(*ruby_command_args("ruby", "-e", "puts Gem::ConfigMap[:ruby_version]"))
350
- end
351
170
  end
352
171
  end
353
172
  end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/match_platform"
4
+
5
+ # Bundler normally raises a "GemNotFound" error when a specification
6
+ # can't be materialized which halts bundler dependency enumeration.
7
+
8
+ # This monkey patch instead creates MissingSpecification objects to
9
+ # identify missing specs without raising errors and halting enumeration.
10
+ # It was the most minimal-touch solution I could think of that should reliably
11
+ # work across many bundler versions
12
+
13
+ module Licensed
14
+ module Bundler
15
+ class MissingSpecification < Gem::BasicSpecification
16
+ include ::Bundler::MatchPlatform
17
+
18
+ attr_reader :name, :version, :platform, :source
19
+ def initialize(name:, version:, platform:, source:)
20
+ @name = name
21
+ @version = version
22
+ @platform = platform
23
+ @source = source
24
+ end
25
+
26
+ def dependencies
27
+ []
28
+ end
29
+
30
+ def gem_dir; end
31
+ def gems_dir
32
+ Gem.dir
33
+ end
34
+ def summary; end
35
+ def homepage; end
36
+
37
+ def error
38
+ "could not find #{name} (#{version}) in any sources"
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ module Bundler
45
+ class LazySpecification
46
+ alias_method :orig_materialize, :__materialize__
47
+ def __materialize__
48
+ spec = orig_materialize
49
+ return spec if spec
50
+
51
+ Licensed::Bundler::MissingSpecification.new(name: name, version: version, platform: platform, source: source)
52
+ end
53
+ end
54
+ end
@@ -98,7 +98,7 @@ module Licensed
98
98
  # Returns whether the package is local to the current project
99
99
  def local_package?(package)
100
100
  return false unless package && package["Dir"]
101
- return false unless File.fnmatch?("#{config.root.to_s}*", package["Dir"])
101
+ return false unless File.fnmatch?("#{config.root.to_s}*", package["Dir"], File::FNM_CASEFOLD)
102
102
  vendored_path_parts(package).nil?
103
103
  end
104
104
 
@@ -69,6 +69,8 @@ module Licensed
69
69
  dependencies.each do |name, dependency|
70
70
  next if dependency["peerMissing"]
71
71
  next if yarn_lock_present && dependency["missing"]
72
+ next if dependency["extraneous"] && dependency["missing"]
73
+
72
74
  dependency["name"] = name
73
75
  (result[name] ||= []) << dependency
74
76
  recursive_dependencies(dependency["dependencies"] || {}, result)
@@ -164,7 +164,7 @@ module Licensed
164
164
  end
165
165
 
166
166
  def project_assets_file_path
167
- File.join(config.pwd, "project.assets.json")
167
+ File.join(config.pwd, nuget_obj_path, "project.assets.json")
168
168
  end
169
169
 
170
170
  def project_assets_file
@@ -172,6 +172,17 @@ module Licensed
172
172
  @project_assets_file = File.read(project_assets_file_path)
173
173
  end
174
174
 
175
+ def project_assets_json
176
+ @project_assets_json ||= JSON.parse(project_assets_file)
177
+ rescue JSON::ParserError => e
178
+ message = "Licensed was unable to read the project.assets.json file. Error: #{e.message}"
179
+ raise Licensed::Sources::Source::Error, message
180
+ end
181
+
182
+ def nuget_obj_path
183
+ config.dig("nuget", "obj_path") || ""
184
+ end
185
+
175
186
  def enabled?
176
187
  File.exist?(project_assets_file_path)
177
188
  end
@@ -180,32 +191,51 @@ module Licensed
180
191
  # Ideally we'd use `dotnet list package` instead, but its output isn't
181
192
  # easily machine readable and doesn't contain everything we need.
182
193
  def enumerate_dependencies
183
- json = JSON.parse(project_assets_file)
184
- nuget_packages_dir = json["project"]["restore"]["packagesPath"]
185
- json["targets"].each_with_object({}) do |(_, target), dependencies|
186
- target.each do |reference_key, reference|
187
- # Ignore project references
188
- next unless reference["type"] == "package"
189
- package_id_parts = reference_key.partition("/")
190
- name = package_id_parts[0]
191
- version = package_id_parts[-1]
192
- id = "#{name}-#{version}"
193
-
194
- # Already know this package from another target
195
- next if dependencies.key?(id)
196
-
197
- path = File.join(nuget_packages_dir, json["libraries"][reference_key]["path"])
198
- dependencies[id] = NuGetDependency.new(
199
- name: id,
200
- version: version,
201
- path: path,
202
- metadata: {
203
- "type" => NuGet.type,
204
- "name" => name
205
- }
206
- )
207
- end
208
- end.values
194
+ reference_keys.map do |reference_key|
195
+ package_id_parts = reference_key.partition("/")
196
+ name = package_id_parts[0]
197
+ version = package_id_parts[-1]
198
+ id = "#{name}-#{version}"
199
+
200
+ path = full_dependency_path(reference_key)
201
+ error = "Package #{id} path was not found in project.assets.json, or does not exist on disk at any project package folder" if path.nil?
202
+
203
+ NuGetDependency.new(
204
+ name: id,
205
+ version: version,
206
+ path: path,
207
+ errors: Array(error),
208
+ metadata: {
209
+ "type" => NuGet.type,
210
+ "name" => name
211
+ }
212
+ )
213
+ end
214
+ end
215
+
216
+ # Returns a unique set of the package reference keys used across all target groups
217
+ def reference_keys
218
+ all_reference_keys = project_assets_json["targets"].flat_map do |_, references|
219
+ references.select { |key, reference| reference["type"] == "package" }
220
+ .keys
221
+ end
222
+
223
+ Set.new(all_reference_keys)
224
+ end
225
+
226
+ # Returns a dependency's path, if it exists, in one of the project's global or fallback package folders
227
+ def full_dependency_path(reference_key)
228
+ dependency_path = project_assets_json.dig("libraries", reference_key, "path")
229
+ return unless dependency_path
230
+
231
+ nuget_package_dirs = [
232
+ project_assets_json.dig("project", "restore", "packagesPath"),
233
+ *Array(project_assets_json.dig("project", "restore", "fallbackFolders"))
234
+ ].compact
235
+
236
+ nuget_package_dirs.map { |dir| File.join(dir, dependency_path) }
237
+ .select { |path| File.directory?(path) }
238
+ .first
209
239
  end
210
240
  end
211
241
  end
@@ -8,7 +8,7 @@ module Licensed
8
8
  module Sources
9
9
  class Pip < Source
10
10
  VERSION_OPERATORS = %w(< > <= >= == !=).freeze
11
- PACKAGE_REGEX = /^([\w-]+)(#{VERSION_OPERATORS.join("|")})?/
11
+ PACKAGE_REGEX = /^([\w\.-]+)(#{VERSION_OPERATORS.join("|")})?/
12
12
 
13
13
  def enabled?
14
14
  return unless virtual_env_pip && Licensed::Shell.tool_available?(virtual_env_pip)
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+ require "json"
3
+ require "pathname"
4
+ require "uri"
5
+
6
+ module Licensed
7
+ module Sources
8
+ class Swift < Source
9
+ def enabled?
10
+ return unless Licensed::Shell.tool_available?("swift") && swift_package?
11
+ File.exist?(package_resolved_file_path)
12
+ end
13
+
14
+ def enumerate_dependencies
15
+ pins.map { |pin|
16
+ name = pin["package"]
17
+ version = pin.dig("state", "version")
18
+ path = dependency_path_for_url(pin["repositoryURL"])
19
+ error = "Unable to determine project path from #{url}" unless path
20
+
21
+ Dependency.new(
22
+ name: name,
23
+ path: path,
24
+ version: version,
25
+ errors: Array(error),
26
+ metadata: {
27
+ "type" => Swift.type,
28
+ "homepage" => homepage_for_url(pin["repositoryURL"])
29
+ }
30
+ )
31
+ }
32
+ end
33
+
34
+ private
35
+
36
+ def pins
37
+ return @pins if defined?(@pins)
38
+
39
+ @pins = begin
40
+ json = JSON.parse(File.read(package_resolved_file_path))
41
+ json.dig("object", "pins")
42
+ rescue => e
43
+ message = "Licensed was unable to read the Package.resolved file. Error: #{e.message}"
44
+ raise Licensed::Sources::Source::Error, message
45
+ end
46
+ end
47
+
48
+ def dependency_path_for_url(url)
49
+ last_path_component = URI(url).path.split("/").last.sub(/\.git$/, "")
50
+ File.join(config.pwd, ".build", "checkouts", last_path_component)
51
+ rescue URI::InvalidURIError
52
+ end
53
+
54
+ def homepage_for_url(url)
55
+ return unless %w{http https}.include?(URI(url).scheme)
56
+ url.sub(/\.git$/, "")
57
+ rescue URI::InvalidURIError
58
+ end
59
+
60
+ def package_resolved_file_path
61
+ File.join(config.pwd, "Package.resolved")
62
+ end
63
+
64
+ def swift_package?
65
+ Licensed::Shell.success?("swift", "package", "describe")
66
+ end
67
+ end
68
+ end
69
+ end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  module Licensed
3
- VERSION = "2.15.0".freeze
3
+ VERSION = "3.1.0".freeze
4
4
 
5
5
  def self.previous_major_versions
6
6
  major_version = Gem::Version.new(Licensed::VERSION).segments.first
@@ -0,0 +1,22 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ if [ -z "$(which swift)" ]; then
5
+ echo "A local swift installation is required for swift development." >&2
6
+ exit 127
7
+ fi
8
+
9
+ swift --version
10
+
11
+ BASE_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
12
+ cd $BASE_PATH/test/fixtures/swift
13
+
14
+ if [ "$1" == "-f" ]; then
15
+ find . -not -regex "\.*" \
16
+ -and -not -path "*/Package.swift" \
17
+ -and -not -path "*/Sources*" \
18
+ -and -not -path "*/Tests*" \
19
+ -print0 | xargs -0 rm -rf
20
+ fi
21
+
22
+ swift package resolve
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: licensed
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.15.0
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitHub
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-03-24 00:00:00.000000000 Z
11
+ date: 2021-06-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: licensee
@@ -243,7 +243,8 @@ files:
243
243
  - docs/adding_a_new_source.md
244
244
  - docs/commands.md
245
245
  - docs/configuration.md
246
- - docs/migrating_to_newer_versions.md
246
+ - docs/migrations/v2.md
247
+ - docs/migrations/v3.md
247
248
  - docs/packaging.md
248
249
  - docs/reporters.md
249
250
  - docs/sources/bower.md
@@ -261,6 +262,7 @@ files:
261
262
  - docs/sources/pip.md
262
263
  - docs/sources/pipenv.md
263
264
  - docs/sources/stack.md
265
+ - docs/sources/swift.md
264
266
  - docs/sources/yarn.md
265
267
  - exe/licensed
266
268
  - lib/licensed.rb
@@ -290,6 +292,7 @@ files:
290
292
  - lib/licensed/sources.rb
291
293
  - lib/licensed/sources/bower.rb
292
294
  - lib/licensed/sources/bundler.rb
295
+ - lib/licensed/sources/bundler/missing_specification.rb
293
296
  - lib/licensed/sources/cabal.rb
294
297
  - lib/licensed/sources/composer.rb
295
298
  - lib/licensed/sources/dep.rb
@@ -304,6 +307,7 @@ files:
304
307
  - lib/licensed/sources/pip.rb
305
308
  - lib/licensed/sources/pipenv.rb
306
309
  - lib/licensed/sources/source.rb
310
+ - lib/licensed/sources/swift.rb
307
311
  - lib/licensed/sources/yarn.rb
308
312
  - lib/licensed/ui/shell.rb
309
313
  - lib/licensed/version.rb
@@ -327,6 +331,7 @@ files:
327
331
  - script/source-setup/nuget
328
332
  - script/source-setup/pip
329
333
  - script/source-setup/pipenv
334
+ - script/source-setup/swift
330
335
  - script/source-setup/yarn
331
336
  - script/test
332
337
  homepage: https://github.com/github/licensed
@@ -348,7 +353,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
348
353
  - !ruby/object:Gem::Version
349
354
  version: '0'
350
355
  requirements: []
351
- rubygems_version: 3.0.3
356
+ rubygems_version: 3.0.3.1
352
357
  signing_key:
353
358
  specification_version: 4
354
359
  summary: Extract and validate the licenses of dependencies.