license_scout 1.3.7 → 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +195 -0
  3. data/bin/license_scout +3 -59
  4. data/bin/mix_lock_json +0 -0
  5. data/bin/rebar_lock_json +0 -0
  6. data/lib/license_scout/cli.rb +99 -0
  7. data/lib/license_scout/collector.rb +25 -77
  8. data/lib/license_scout/config.rb +94 -0
  9. data/lib/license_scout/data/dependeny_manifest_v2_schema.json +62 -0
  10. data/lib/license_scout/data/exceptions.json +306 -0
  11. data/lib/license_scout/data/licenses.json +4653 -0
  12. data/lib/license_scout/dependency.rb +79 -7
  13. data/lib/license_scout/dependency_manager/base.rb +74 -42
  14. data/lib/license_scout/dependency_manager/berkshelf.rb +25 -50
  15. data/lib/license_scout/dependency_manager/bundler/_bundler_script.rb +1 -1
  16. data/lib/license_scout/dependency_manager/bundler.rb +47 -69
  17. data/lib/license_scout/dependency_manager/cpanm.rb +62 -112
  18. data/lib/license_scout/dependency_manager/dep.rb +29 -36
  19. data/lib/license_scout/dependency_manager/glide.rb +25 -36
  20. data/lib/license_scout/dependency_manager/godep.rb +27 -26
  21. data/lib/license_scout/dependency_manager/habitat.rb +126 -0
  22. data/lib/license_scout/dependency_manager/mix.rb +105 -0
  23. data/lib/license_scout/dependency_manager/npm.rb +30 -86
  24. data/lib/license_scout/dependency_manager/rebar.rb +26 -45
  25. data/lib/license_scout/dependency_manager.rb +19 -5
  26. data/lib/license_scout/exceptions.rb +2 -43
  27. data/lib/license_scout/license.rb +126 -0
  28. data/lib/license_scout/{license_file_analyzer.rb → log.rb} +4 -6
  29. data/lib/license_scout/reporter.rb +149 -55
  30. data/lib/license_scout/spdx.rb +123 -0
  31. data/lib/license_scout/version.rb +1 -1
  32. data/lib/license_scout.rb +2 -0
  33. data/native_parsers/mix_lock_json/README.md +21 -0
  34. data/native_parsers/mix_lock_json/lib/mix_lock_json.ex +20 -0
  35. data/native_parsers/mix_lock_json/mix.exs +31 -0
  36. data/native_parsers/mix_lock_json/mix.lock +3 -0
  37. data/{erl_src → native_parsers}/rebar_lock_json/rebar.lock +2 -2
  38. metadata +144 -67
  39. data/lib/license_scout/canonical_licenses/BSD-2-Clause.txt +0 -19
  40. data/lib/license_scout/canonical_licenses/BSD-3-Clause.txt +0 -27
  41. data/lib/license_scout/canonical_licenses/BSD-4-Clause.txt +0 -31
  42. data/lib/license_scout/canonical_licenses/Chef-MLSA.txt +0 -5
  43. data/lib/license_scout/canonical_licenses/ISC.txt +0 -14
  44. data/lib/license_scout/canonical_licenses/MIT.txt +0 -20
  45. data/lib/license_scout/dependency_manager/bundler/LICENSE.md +0 -23
  46. data/lib/license_scout/dependency_manager/json/README.md +0 -392
  47. data/lib/license_scout/dependency_manager/manual.rb +0 -67
  48. data/lib/license_scout/license_file_analyzer/any_matcher.rb +0 -37
  49. data/lib/license_scout/license_file_analyzer/definitions.rb +0 -219
  50. data/lib/license_scout/license_file_analyzer/header_matcher.rb +0 -34
  51. data/lib/license_scout/license_file_analyzer/matcher.rb +0 -46
  52. data/lib/license_scout/license_file_analyzer/template.rb +0 -45
  53. data/lib/license_scout/license_file_analyzer/templates/Apache2-short.txt +0 -11
  54. data/lib/license_scout/license_file_analyzer/templates/Apache2.txt +0 -170
  55. data/lib/license_scout/license_file_analyzer/templates/BSD-2-Clause-bullets.txt +0 -18
  56. data/lib/license_scout/license_file_analyzer/templates/BSD-2-Clause.txt +0 -19
  57. data/lib/license_scout/license_file_analyzer/templates/BSD-3-Clause-alt-format.txt +0 -24
  58. data/lib/license_scout/license_file_analyzer/templates/BSD-3-Clause.txt +0 -21
  59. data/lib/license_scout/license_file_analyzer/templates/BSD.txt +0 -24
  60. data/lib/license_scout/license_file_analyzer/templates/Chef-MLSA.txt +0 -5
  61. data/lib/license_scout/license_file_analyzer/templates/EPLICENSE.txt +0 -286
  62. data/lib/license_scout/license_file_analyzer/templates/GPL-2.0.txt +0 -339
  63. data/lib/license_scout/license_file_analyzer/templates/GPL-3.0.txt +0 -674
  64. data/lib/license_scout/license_file_analyzer/templates/ISC.txt +0 -2
  65. data/lib/license_scout/license_file_analyzer/templates/LGPL-3.0.txt +0 -165
  66. data/lib/license_scout/license_file_analyzer/templates/MIT.txt +0 -9
  67. data/lib/license_scout/license_file_analyzer/templates/MPL2.txt +0 -373
  68. data/lib/license_scout/license_file_analyzer/templates/Python-2.0.txt +0 -47
  69. data/lib/license_scout/license_file_analyzer/templates/Ruby.txt +0 -52
  70. data/lib/license_scout/license_file_analyzer/text.rb +0 -46
  71. data/lib/license_scout/net_fetcher.rb +0 -106
  72. data/lib/license_scout/options.rb +0 -47
  73. data/lib/license_scout/overrides.rb +0 -1120
  74. /data/{erl_src → native_parsers}/rebar_lock_json/README.md +0 -0
  75. /data/{erl_src → native_parsers}/rebar_lock_json/rebar.config +0 -0
  76. /data/{erl_src → native_parsers}/rebar_lock_json/src/rebar_lock_json.app.src +0 -0
  77. /data/{erl_src → native_parsers}/rebar_lock_json/src/rebar_lock_json.erl +0 -0
@@ -16,18 +16,90 @@
16
16
  #
17
17
 
18
18
  module LicenseScout
19
- Dependency = Struct.new(:name, :version, :license, :license_files, :dep_mgr_name) do
19
+ class Dependency
20
20
 
21
+ attr_reader :name
22
+
23
+ attr_reader :version
24
+
25
+ attr_reader :path
26
+
27
+ attr_reader :type
28
+
29
+ attr_reader :license
30
+
31
+ def initialize(name, version, path, type)
32
+ @name = name
33
+ @version = version
34
+ @path = path
35
+ @type = type
36
+
37
+ if path.nil?
38
+ @license = LicenseScout::License.new
39
+ elsif path =~ /^http/ || File.directory?(path)
40
+ @license = LicenseScout::License.new(path)
41
+ else
42
+ raise LicenseScout::Exceptions::MissingSourceDirectory.new("Could not find the source for '#{name}' in the following directories:\n\t * #{path}")
43
+ end
44
+
45
+ fallbacks = LicenseScout::Config.fallbacks.send(type.to_sym).select { |f| f["name"] =~ uid_regexp }
46
+ fallbacks.each do |fallback|
47
+ license.add_license(fallback["license_id"], "license_scout fallback", fallback["license_file"], force: true)
48
+ end
49
+ end
50
+
51
+ # @return [String] The UID for this dependency. Example: bundler (1.16.1)
52
+ def uid
53
+ "#{name} (#{version})"
54
+ end
55
+
56
+ # @return [Regexp] The regular expression that can be used to identify this dependency
57
+ def uid_regexp
58
+ Regexp.new("#{Regexp.escape(name)}(\s+\\(#{Regexp.escape(version)}\\))?")
59
+ end
60
+
61
+ def exceptions
62
+ @exceptions ||= LicenseScout::Config.exceptions.send(type.to_sym).select { |e| e["name"] =~ uid_regexp }
63
+ end
64
+
65
+ # Capture a license that was specified in metadata
66
+ #
67
+ # @param license_id [String] The license as specified in the metadata file
68
+ # @param source [String] Where we found the license info
69
+ # @param contents_url [String] Where we can find the contents of the license
70
+ #
71
+ # @return [void]
72
+ def add_license(license_id, source, contents_url = nil)
73
+ LicenseScout::Log.debug("[#{type}] Adding #{license_id} license for #{name} from #{source}")
74
+ license.add_license(license_id, source, contents_url, {})
75
+ end
76
+
77
+ # Determine if this dependency has an exception. Will match an exception for both the name and the name+version
78
+ def has_exception?
79
+ exceptions.any?
80
+ end
81
+
82
+ def exception_reason
83
+ if has_exception?
84
+ exceptions.first.dig("reason")
85
+ else
86
+ nil
87
+ end
88
+ end
89
+
90
+ # Be able to sort dependencies by type, then name, then version
91
+ def <=>(other)
92
+ "#{type}#{name}#{version}" <=> "#{other.type}#{other.name}#{other.version}"
93
+ end
94
+
95
+ # @return [Boolean] Whether or not this object is equal to another one. Used for Set uniqueness.
21
96
  def eql?(other)
22
- other.is_a?(self.class) && other.hash == hash
97
+ other.kind_of?(self.class) && other.hash == hash
23
98
  end
24
99
 
25
- # hash code for when Dependency is used as a key in a Hash or member of a
26
- # Set. The implementation is somewhat naive, but will work fine if you
27
- # don't go too crazy mixing different types.
100
+ # @return [Integer] A hashcode that can be used to idenitfy this object. Used for Set uniqueness.
28
101
  def hash
29
- [dep_mgr_name, name, version, license].hash
102
+ [type, name, version].hash
30
103
  end
31
-
32
104
  end
33
105
  end
@@ -15,58 +15,90 @@
15
15
  # limitations under the License.
16
16
  #
17
17
 
18
+ require "licensee"
18
19
  require "license_scout/dependency"
19
- require "license_scout/license_file_analyzer"
20
20
 
21
21
  module LicenseScout
22
+ # The DependencyManager module (or more accurately, implementations of it) are responsible for recognizing
23
+ # when a dependency manager such as Bundler, Rebar, Berkshelf, etc is managing dependencies for source code
24
+ # in the given directory.
22
25
  module DependencyManager
23
26
  class Base
24
27
 
25
- POSSIBLE_LICENSE_FILES = %w{
26
- LICENSE
27
- LICENSE.txt
28
- LICENSE.TXT
29
- LICENSE.md
30
- LICENSE.mkd
31
- LICENSE.rdoc
32
- License
33
- License.text
34
- License.txt
35
- License.md
36
- License.rdoc
37
- Licence.rdoc
38
- Licence.md
39
- license
40
- LICENCE
41
- licence
42
- license.md
43
- licence.md
44
- APACHE.LICENSE
45
- MIT-LICENSE
46
- MIT-LICENSE.txt
47
- LICENSE.MIT
48
- LICENSE-MIT
49
- LICENSE-MIT.txt
50
- LGPL-2.1
51
- COPYING.txt
52
- COPYING
53
- BSD_LICENSE
54
- LICENSE.BSD
55
- UNLICENSE
56
- }.freeze
28
+ attr_reader :directory
57
29
 
58
- attr_reader :project_dir
59
- attr_reader :options
30
+ # @param directory [String] The fully-qualified path to the directory to be inspected
31
+ def initialize(directory)
32
+ @directory = directory
33
+ @deps = nil
34
+ end
35
+
36
+ # The unique name of this Dependency Manager. In general, the name should follow the `<TYPE>_<NAME` pattern where:
37
+ # * <TYPE> is the value of DependencyManager#type
38
+ # * <NAME> is the name of the dependency manager.
39
+ #
40
+ # @example Go's various package managers
41
+ # Name Reference
42
+ # -------- -----------------------------------------------
43
+ # go_dep [`godep`](https://github.com/tools/godep)
44
+ # go_godep [`dep`](https://github.com/golang/dep)
45
+ # go_glide [`glide`](https://github.com/Masterminds/glide)
46
+ #
47
+ # @return [String]
48
+ def name
49
+ raise LicenseScout::Exceptions::Error.new("All DependencyManagers must have a `#name` method")
50
+ end
51
+
52
+ # The "type" of dependencies this manager manages. This can be the language, tool, etc.
53
+ #
54
+ # @return [String]
55
+ def type
56
+ raise LicenseScout::Exceptions::Error.new("All DependencyManagers must have a `#type` method")
57
+ end
58
+
59
+ # A human-readable description of the files/folders that indicate this dependency manager is in use.
60
+ #
61
+ # @return [String]
62
+ def signature
63
+ raise LicenseScout::Exceptions::Error.new("All DependencyManagers must have a `#signature` method")
64
+ end
65
+
66
+ # Whether or not we were able to detect that this dependency manager is currently in use in our directory
67
+ #
68
+ # @return [Boolean]
69
+ def detected?
70
+ raise LicenseScout::Exceptions::Error.new("All DependencyManagers must have a `#detected?` method")
71
+ end
60
72
 
61
- def initialize(project_dir, options)
62
- @project_dir = project_dir
63
- @options = options
73
+ # The command to run to install dependency if one or more is missing
74
+ #
75
+ # @return [String]
76
+ def install_command
77
+ raise LicenseScout::Exceptions::Error.new("All DependencyManagers must have a `#install_command` method")
64
78
  end
65
79
 
66
- def create_dependency(dep_name, version, license, license_files, dep_mgr_name = name)
67
- # add name of the dependency manager `name` to the dependency we are
68
- # creating.
69
- Dependency.new(dep_name, version, license, license_files, dep_mgr_name)
80
+ # Implementation's of this method in sub-classes are the methods that are responsible for all
81
+ # the heavy-lifting when it comes to determining the dependencies (and their licenses).
82
+ # They should return an array of `LicenseScout::Dependency`.
83
+ #
84
+ # @return [Array<LicenseScout::Dependency>]
85
+ def dependencies
86
+ []
87
+ end
88
+
89
+ private
90
+
91
+ # A helper that allows you to quickly create a new Dependency (with the type)
92
+ #
93
+ # @param name [String] The name of the dependency
94
+ # @param version [String] The version of the dependency
95
+ # @param path [String] The path to the dependency on the local system
96
+ #
97
+ # @return [LicenseScout::Dependency]
98
+ # @api private
99
+ def new_dependency(name, version, path)
100
+ LicenseScout::Log.debug("[#{type}] Found #{name} #{version}#{" #{path}" unless path.nil?}")
101
+ Dependency.new(name, version, path, type)
70
102
  end
71
103
  end
72
104
  end
@@ -25,14 +25,16 @@ module LicenseScout
25
25
  "chef_berkshelf"
26
26
  end
27
27
 
28
- def berkshelf_available?
29
- begin
30
- require "berkshelf"
31
- rescue LoadError
32
- return false
33
- end
28
+ def type
29
+ "chef_cookbook"
30
+ end
34
31
 
35
- true
32
+ def signature
33
+ "Berksfile and Berksfile.lock files"
34
+ end
35
+
36
+ def install_command
37
+ "berks install"
36
38
  end
37
39
 
38
40
  def detected?
@@ -41,13 +43,12 @@ module LicenseScout
41
43
 
42
44
  def dependencies
43
45
  unless berkshelf_available?
44
- raise LicenseScout::Exceptions::Error.new "Project at '#{project_dir}' is a Berkshelf project but berkshelf gem is not available in your bundle. Add berkshelf to your bundle in order to collect licenses for this project."
46
+ raise LicenseScout::Exceptions::Error.new("Project at '#{directory}' is a Berkshelf project but berkshelf gem is not available in your bundle. Add berkshelf to your bundle in order to collect licenses for this project.")
45
47
  end
46
48
 
47
- dependencies = []
48
- cookbook_dependencies = nil
49
+ cookbook_dependencies = []
49
50
 
50
- Dir.chdir(project_dir) do
51
+ Dir.chdir(directory) do
51
52
  berksfile = ::Berkshelf::Berksfile.from_file("./Berksfile")
52
53
 
53
54
  # Berkshelf should not give an error when there are cookbooks in the
@@ -59,56 +60,30 @@ module LicenseScout
59
60
  cookbook_dependencies = berksfile.list
60
61
  end
61
62
 
62
- cookbook_dependencies.each do |dep|
63
- dependency_name = dep.name
64
- dependency_version = dep.cached_cookbook.version
65
-
66
- dependency_license_files = auto_detect_license_files(dep.cached_cookbook.path.to_s)
67
-
68
- # Check license override and license_files override separately since
69
- # only one might be set in the overrides.
70
- dependency_license = options.overrides.license_for(name, dependency_name, dependency_version) || dep.cached_cookbook.license
71
-
72
- override_license_files = options.overrides.license_files_for(name, dependency_name, dependency_version)
73
- cookbook_path = dep.cached_cookbook.path.to_s
63
+ cookbook_dependencies.map do |dep|
64
+ new_dependency(dep.name, dep.cached_cookbook.version, dep.cached_cookbook.path.to_s)
65
+ end.compact
66
+ end
74
67
 
75
- if override_license_files.empty?
76
- dependency_license_files = auto_detect_license_files(cookbook_path)
77
- else
78
- dependency_license_files = override_license_files.resolve_locations(cookbook_path)
79
- end
68
+ private
80
69
 
81
- dependencies << create_dependency(
82
- dependency_name,
83
- dependency_version,
84
- dependency_license,
85
- dependency_license_files
86
- )
70
+ def berkshelf_available?
71
+ begin
72
+ require "berkshelf"
73
+ rescue LoadError
74
+ return false
87
75
  end
88
76
 
89
- dependencies
77
+ true
90
78
  end
91
79
 
92
- private
93
-
94
80
  def berksfile_path
95
- File.join(project_dir, "Berksfile")
81
+ File.join(directory, "Berksfile")
96
82
  end
97
83
 
98
84
  def lockfile_path
99
- File.join(project_dir, "Berksfile.lock")
85
+ File.join(directory, "Berksfile.lock")
100
86
  end
101
-
102
- def auto_detect_license_files(cookbook_path)
103
- unless File.exist?(cookbook_path)
104
- raise LicenseScout::Exceptions::InaccessibleDependency.new "Autodetected cookbook path '#{cookbook_path}' does not exist"
105
- end
106
-
107
- Dir.glob("#{cookbook_path}/*").select do |f|
108
- POSSIBLE_LICENSE_FILES.include?(File.basename(f))
109
- end
110
- end
111
-
112
87
  end
113
88
  end
114
89
  end
@@ -30,7 +30,7 @@
30
30
  require "bundler/setup"
31
31
 
32
32
  # We're only using things that are in the stdlib.
33
- require "json" unless defined?(JSON)
33
+ require "json"
34
34
 
35
35
  dependencies = []
36
36
 
@@ -16,13 +16,11 @@
16
16
  #
17
17
 
18
18
  require "license_scout/dependency_manager/base"
19
- require "license_scout/net_fetcher"
20
- require "license_scout/exceptions"
21
19
 
22
20
  require "bundler"
23
- require "mixlib/shellout" unless defined?(Mixlib::ShellOut)
24
- require "ffi_yajl" unless defined?(FFI_Yajl)
25
- require "pathname" unless defined?(Pathname)
21
+ require "mixlib/shellout"
22
+ require "ffi_yajl"
23
+ require "pathname"
26
24
 
27
25
  module LicenseScout
28
26
  module DependencyManager
@@ -32,6 +30,18 @@ module LicenseScout
32
30
  "ruby_bundler"
33
31
  end
34
32
 
33
+ def type
34
+ "ruby"
35
+ end
36
+
37
+ def signature
38
+ "Gemfile and Gemfile.lock files"
39
+ end
40
+
41
+ def install_command
42
+ "bundle install"
43
+ end
44
+
35
45
  def detected?
36
46
  # We check the existence of both Gemfile and Gemfile.lock. We need both
37
47
  # of them to be able to get a concrete set of dependencies which we can
@@ -42,13 +52,40 @@ module LicenseScout
42
52
  File.exist?(gemfile_path) && File.exist?(lockfile_path)
43
53
  end
44
54
 
55
+ def dependencies
56
+ dependency_data.map do |gem_data|
57
+ dep_name = gem_data["name"]
58
+ dep_version = gem_data["version"]
59
+
60
+ dep_path = if dep_name == "bundler"
61
+ # Bundler is weird. It inserts itself as a dependency, but is a
62
+ # special case, so rubygems cannot correctly report the license.
63
+ # Additionally, rubygems reports the gem path as a path inside
64
+ # bundler's lib/ dir, so we have to munge it.
65
+ "https://github.com/bundler/bundler"
66
+ elsif dep_name == "json"
67
+ # json is different weird. When project is using the json that is prepackaged with
68
+ # Ruby, its included not as a full fledged gem but an *.rb file at:
69
+ # /opt/opscode/embedded/lib/ruby/2.2.0/json.rb
70
+ # Because of this its license is reported as nil and its license files can not be
71
+ # found. That is why we need to provide them manually here.
72
+ "https://github.com/flori/json"
73
+ else
74
+ gem_data["path"]
75
+ end
76
+
77
+ new_dependency(dep_name, dep_version, dep_path)
78
+ end.compact
79
+ end
80
+
81
+ private
82
+
45
83
  def dependency_data
46
84
  bundler_script = File.join(File.dirname(__FILE__), "bundler/_bundler_script.rb")
47
85
 
48
- Dir.chdir(project_dir) do
86
+ Dir.chdir(directory) do
49
87
  json_dep_data = with_clean_env do
50
- ruby_bin_path = options.ruby_bin || "ruby"
51
- s = Mixlib::ShellOut.new("#{ruby_bin_path} #{bundler_script}", environment: options.environment)
88
+ s = Mixlib::ShellOut.new("#{LicenseScout::Config.ruby_bin} #{bundler_script}", environment: LicenseScout::Config.environment)
52
89
  s.run_command
53
90
  s.error!
54
91
  s.stdout
@@ -57,55 +94,6 @@ module LicenseScout
57
94
  end
58
95
  end
59
96
 
60
- def dependencies
61
- dependencies = []
62
- dependency_data.each do |gem_data|
63
- dependency_name = gem_data["name"]
64
- dependency_version = gem_data["version"]
65
- dependency_license = nil
66
- dependency_license_files = []
67
-
68
- if dependency_name == "bundler"
69
- # Bundler is weird. It inserts itself as a dependency, but is a
70
- # special case, so rubygems cannot correctly report the license.
71
- # Additionally, rubygems reports the gem path as a path inside
72
- # bundler's lib/ dir, so we have to munge it.
73
- dependency_license = "MIT"
74
- dependency_license_files = [File.join(File.dirname(__FILE__), "bundler/LICENSE.md")]
75
- elsif dependency_name == "json"
76
- # json is different weird. When project is using the json that is prepackaged with
77
- # Ruby, its included not as a full fledged gem but an *.rb file at:
78
- # /opt/opscode/embedded/lib/ruby/2.2.0/json.rb
79
- # Because of this its license is reported as nil and its license files can not be
80
- # found. That is why we need to provide them manually here.
81
- dependency_license = "Ruby"
82
- dependency_license_files = [File.join(File.dirname(__FILE__), "json/README.md")]
83
- else
84
- # Check license override and license_files override separately since
85
- # only one might be set in the overrides.
86
- dependency_license = options.overrides.license_for(name, dependency_name, dependency_version) || gem_data["license"]
87
-
88
- override_license_files = options.overrides.license_files_for(name, dependency_name, dependency_version)
89
- if override_license_files.empty?
90
- dependency_license_files = auto_detect_license_files(gem_data["path"])
91
- else
92
- dependency_license_files = override_license_files.resolve_locations(gem_data["path"])
93
- end
94
- end
95
-
96
- dependencies << create_dependency(
97
- dependency_name,
98
- dependency_version,
99
- dependency_license,
100
- dependency_license_files
101
- )
102
- end
103
-
104
- dependencies
105
- end
106
-
107
- private
108
-
109
97
  #
110
98
  # Execute the given command, removing any Ruby-specific environment
111
99
  # variables. This is an "enhanced" version of +Bundler.with_clean_env+,
@@ -142,22 +130,12 @@ module LicenseScout
142
130
  ENV.replace(original.to_hash)
143
131
  end
144
132
 
145
- def auto_detect_license_files(gem_path)
146
- unless File.exist?(gem_path)
147
- raise LicenseScout::Exceptions::InaccessibleDependency.new "Autodetected gem path '#{gem_path}' does not exist"
148
- end
149
-
150
- Dir.glob("#{gem_path}/*").select do |f|
151
- POSSIBLE_LICENSE_FILES.include?(File.basename(f))
152
- end
153
- end
154
-
155
133
  def gemfile_path
156
- File.join(project_dir, "Gemfile")
134
+ File.join(directory, "Gemfile")
157
135
  end
158
136
 
159
137
  def lockfile_path
160
- File.join(project_dir, "Gemfile.lock")
138
+ File.join(directory, "Gemfile.lock")
161
139
  end
162
140
 
163
141
  end