bundler icon indicating copy to clipboard operation
bundler copied to clipboard

Prevent circular symlinks to working directory

Open thinkerbot opened this issue 15 years ago • 1 comments

I've been thinking more about the 'circular symlink' thing and I think the approach is desirable even though the symlink isn't. (regarding http://github.com/wycats/bundler/issues/closed#issue/64)

Say you don't do the circular approach:

[sample.gemspec]
Gem::Specification.new do |s|
  s.name = "sample"
  s.version = "1.0"
  s.add_dependency "rack", "= 1.0.0"
end

[Gemfile]
spec = eval File.read("sample.gemspec")
spec.dependencies.each do |dep|
  options = {}
  options[:only] = :testing if dep.type == :development

  gem dep.name, dep.version_requirements, options
end

When you bundle this your environment.rb file looks like this:

[vendor/gems/environment.rb]
...
$LOAD_PATH.unshift File.expand_path("#{dir}/gems/rack-1.0.0/bin")
$LOAD_PATH.unshift File.expand_path("#{dir}/gems/rack-1.0.0/lib")

...
@bundled_specs["rack"] = eval(File.read("#{dir}/specifications/rack-1.0.0.gemspec"))
@bundled_specs["rack"].loaded_from = "#{dir}/specifications/rack-1.0.0.gemspec"

What's missing is are the lib and bin paths for the working directory (ie for the sample project). This prevents you from using the environment.rb file to setup your full environment; at some point you need to add whatever local load paths you need.

Instead you could go the route of depending on yourself, which isn't as circular as it sounds because you're really just making an environment.rb that includes the local load paths... there's actually nothing circular going on.

[Gemfile]
spec = eval File.read("sample.gemspec")
gem spec.name, spec.version

Produces:

[vendor/gems/environment.rb]
...
$LOAD_PATH.unshift File.expand_path("#{dir}/../../bin")
$LOAD_PATH.unshift File.expand_path("#{dir}/../../lib")
$LOAD_PATH.unshift File.expand_path("#{dir}/gems/rack-1.0.0/bin")
$LOAD_PATH.unshift File.expand_path("#{dir}/gems/rack-1.0.0/lib")

...
@bundled_specs["sample"] = eval(File.read("#{dir}/specifications/sample-1.0.gemspec"))
@bundled_specs["sample"].loaded_from = "#{dir}/specifications/sample-1.0.gemspec"
@bundled_specs["rack"] = eval(File.read("#{dir}/specifications/rack-1.0.0.gemspec"))
@bundled_specs["rack"].loaded_from = "#{dir}/specifications/rack-1.0.0.gemspec"

Now you can use a test task like this:

[Rakefile]
task :test do
  test = Dir.glob('test/**/*_test.rb')
  cmd = ['ruby', '-w', '-rvendor/gems/environment.rb', '-e', 'ARGV.dup.each {|test| load test}'] + tests
  system(*cmd)
end

In this setup the entirety of your environment is handled by your gemspec + bundler, which I think is not only slick but adds to the completeness of the sandbox.

Again the only practical issue of this approach is the circular symlink which messes up text editors. A check can prevent the creation of the symlink. Omitting the symlink doesn't appear to break anything because it isn't actually used in the environment.rb file. The patch is here for your consideration: bahuvrihi@3731b5a96cd9b6c56a93d0eab003b373ba2d7084

thinkerbot avatar Oct 09 '09 16:10 thinkerbot

Oops, note that I originally put down the wrong link to the patch. I updated the link and now it's correct.

thinkerbot avatar Oct 09 '09 16:10 thinkerbot