How can I develop two gems together with Bundler? - ruby

I am developing two gems, let's call them foo and bar. Also foo has a runtime dependency on bar. I am developing both these gems with bundler.
How can I specify bar as a dependency of foo and have bundler resolve that dependency to a local path, without polluting my Gemfile? At the moment the only way I can see to do this is to put gem "bar", path: "path/to/bar" in foo's Gemfile, and remove it once bar is on rubygems, but this solution is obviously unsatisfactory as it will break on anybody else's machine until that date.

It is not clear if the dependency is runtime or compile time.
Have you tried installing bar using the local .gem file? Once installed in GEM_HOME, your foo gem should detect it
cd /path/to/bar
rake install
then
cd /path/to/foo
rake build

You can specify the dependency as a git branch, and then map it to a local path using the instructions here: http://ryanbigg.com/2013/08/bundler-local-paths/

Related

Bundler config to either look for gems in custom path or download from custom source

How do I configure bundler so that when I run bundle install it looks for gems under /my/custom/path first and if it doesn't find there then try to fetch them from a ruby gem remote repository hosted under https://a.nice.host and downloads those into ./local/relative/path (relative to cwd for example). I would like to avoid the bundler looking at default gem installation system path or rubygems.org
The syntax for sourcing a gem from a local folder is:
gem 'some-gem-name', path: '/my/custom/path'
And the syntax for specifying a custom source is:
gem 'another-gem-name', source: 'https://a.nice.host'
And to install gems into a specific local folder, you can run:
bundle install --path ./local/relative/path
Now, that's probably all the tools you need, in truth... (And in fact, especially for that last requirement, you may instead wish to look into rvm gemsets, or using bundle install --deployment.)
But you did also ask about "looking in a local folder first, and only falling back to a remote source if it doesn't exist". That's quite an odd requirement (usually you'd only want to explicitly opt-in to fetching gems from a local path?!), but to answer this question as you've asked it...
A Gemfile is literally just ruby code! So you can define this logic using... You guessed it, ruby! For example:
if File.exists?('/my/custom/path')
gem 'some-gem-name', path: '/my/custom/path'
else
gem 'some-gem-name', source: 'https://a.nice.host'
end
If this (unusual) pattern needs to be repeated in multiple places, you could wrap it into some helper method.
For more information on the configuration options of bundler, please see the documentation.

Is it possible to override gemfile for local development?

We have a Gemfile currently in our git repository. However, there's a gem I use only locally in my environment (my team doesn't use it). In order to use it, I have to add it to our Gemfile, but every time I check out to our master/dev main branch, I have to remove it because of conflicts with the tracked gemfile.
What I would like is something like a Gemfile.local which would inherit the gems imported from the Gemfile but to also allow new gems to be imported there to use on my machine only. This file would be ignored in .gitignore. Is this even possible?
Set BUNDLE_GEMFILE environment variable:
BUNDLE_GEMFILE=Gemfile.local bundle exec rails c
To provide a “delta” only in the Gemfile.local put require_relative 'Gemfile' on top of it (or Bundler::Dsl#eval_gemfile as suggested by #MichaelKohl in comments.)
Put below code at the top of your Gemfile.local to load existing gemfile:
if File.exists?('Gemfile') then
eval File.read('Gemfile')
end
It will load all gems from existing Gemfile. You can add new gems also as you need.
Run below commands to install gems from new Gemfile.local:
bundle install --gemfile=Gemfile.local

Ruby gem equivalent of "pip install -e"?

In Python I can install a package from source in "editable" mode using pip install -e. Then I can carry on editing the code, and any changes will be automatically picked by other Python scripts that import library
Is there a comparable workflow for developing Ruby gems? What is the "Ruby way" of using libs as they are being developed rather than, for example, compiling and installing a gem every time I make a change to the source?
There are two common approaches one might use with bundler:
one executes bundle install --path vendor/bundle and does not run bundle update unless everything is tested.
one tells a bundler to use a local version of the gem:
in Gemfile (this is not supported in mymaingem.gemspec due to rubygems maintainence issues): gem 'mycutegem', :git => 'git://github.com/myname/mycutegem', :branch => 'master';
in command line: bundle config local.mycutegem /path_to_local_git/mycutegem.
The first approach will download everything into subfolder of your current project (here it’d be vendor/bundle.) Feel free to modify everything there, it’ll be reflected.
The second approach is likely better. You are to clone the gem from github and instruct bundle to use your local clone of the respective git repository. This approach provides you with an ability to publish the changes to your main gem into the repository. As soon as dependent repo is published as well, the up-to-date version will be retrieven by your gem subscribers, assuming they have not instructed their bundlers to use their locals.
Hope this helps.
Let's assume you have your gem code residing in a folder (say my_project/mygem/lib). You have some Ruby code in my_project that you want using the mygem code.
What I'd do is add mygem/lib to the $LOAD_PATH global variable in the beginning of said files. Kinda like this:
$LOAD_PATH << File.expand_path('lib', './mygem') # Resolve global paths
require 'a_file' # Would require "mygem/lib/a_file.rb"
I am not sure if this is exactly what you want to achieve, but from the description I infer that you want to have a local copy of some gem and reference that gem in your project.
If this is the case, you can (usually) achieve it in two steps:
Clone the gem from VCS (in most cases: git), e.g.
git clone url-of-the-gem-repo
Reference the local copy using Bundler :path, e.g.
gem "some-gem", :path => "/path/to/local/copy"
If the gem is stored at github, an even better way is to first fork it at github and then clone your own copy. Then, if you provide any improvements to the code in the local repo, you can easily share it with the world using a pull request.
I realize this is a 5 year old question, but I found all of these answers unsatisfying. Since I use ruby to develop CLI tools, using bundler for testing is not ideal. I need to be able to execute my test commands anywhere, and get the actual equivalent of pip install --editable.
Here's my solution.
Use RVM to setup a new gemset for development
rvm gemset use tools-dev --create
Install your gems
gem install mygem.gem
Locate the installation directory
gem list tool -d
In the output, it will say installed at ${rvm_gemset_path}. Copy that path. It will also be an environment variable GEM_HOME, but I include this step for completeness and clarity.
Delete the installed copy
rm -Rf ${rvm_gemset_path}/gems/mygem-${version}
Create a symlink to the git repository working directory.
ln -s ${PWD} ${rvm_gemset_path}/gems/mygem-${version}
This bash script will do it in a more automated fashion assuming that you are in the git directory and you've set GEM_NAME as your gem's name.
rvm gemset use --create ${GEM_NAME}-dev
gem build ${GEM_NAME}.gemspec
gem install ${GEM_NAME}*.gem
gem_install_path=$(ruby -e "puts Gem::Specification.find_by_name('${GEM_NAME}').full_gem_path")
mv $gem_install_path "${gem_install_path}.bak"
ln -s $PWD $gem_install_path

How to use gems not in a Gemfile when working with bundler?

When using bundler with a project in general and Rails specifically, you have access only to gems defined in your Gemfile. While this makes sense, it can be limiting. Mostly I find it limiting when I want to use a certain RSpec formatter that the rest of the team doesn't use. Unless it's in the Gemfile, it isn't accessible.
Any way around it or I have to add it to Gemfile?
Update: my problem wasn't Bundler but Spork. When running RSpec without Spork I had no problem of using whatever formatter I wanted.
Update #2: it looks like that using Bundler is still the cause of the problem. The difference between using Spork and not using Spork, is that when running RSpec without Spork, it loads the formatter before loading your project and getting into the Bundler "sandbox".
With Bundler:
$ bundle exec irb
>> require 'fivemat'
LoadError: cannot load such file -- fivemat
from (irb):1:in `require'
from (irb):1
from /Users/arikfr/.rvm/rubies/ruby-1.9.3-p194/bin/irb:16:in `<main>'
Without Bundler:
$ irb
>> require 'fivemat'
=> true
In ChiliProject we allow users to create a Gemfile.local which is included into the main Gemfile on load. This allows users to specify additional gems without having to change our Gemfile to ease updates.
For that, we have included the following code at the bottom of our Gemfile.
gemfile_local = File.expand_path('Gemfile.local', __dir__)
if File.readable?(gemfile_local)
puts "Loading #{gemfile_local}..." if $DEBUG
instance_eval(File.read(gemfile_local))
end
The Gemfile.local itself is excluded from the repository via .gitignore.
I assume that none of these answers have been chosen as correct because they don't do a great job of solving the problem: having additional gems that you can use that by default don't require any changes to files already in the repository to achieve. That is, you don't have to modify any files, and you don't have to live with remembering not to check in your local changes. Here's how I do it.
The idea is basically inverting the dependencies of Holger's answer, such that there's no need to modify the shared Gemfile. Bundler allows one to specify which file is to be used as the gemfile, but strangely the documented methods do not apparently work with its configuration file and will not be fixed. There is a somewhat obscured feature of Bundler that any of the configuration options can be set in an environment variable or passed on the command line. Running all of your commands as bundle [command] --gemfile [yourgemfile] or BUNDLE_GEMFILE="[yourgemfile]" bundle [command] will cause Bundler to read whatever gemfile you want it to. I highly recommend using the environment variable approach, and either creating an alias or exporting the variable for your current session, particularly as I was unable to use the command line switch with the "exec" command.
Therefore, I run rspec like this: BUNDLE_GEMFILE="[mygemfile]" bundle exec rspec [filename], and I have the first part of this aliased as bem in my bashrc. Works like a charm.
Then, you should setup your source control to ignore your Gemfile, either in the project's .gitignore or, to keep the project entirely hygienic without changing even its .gitignore, to your personal global ignore file (which is by default in ~/.config/git/ignore and has the same format as a project's gitignore file).
One other thing to note is that Bundler will create a lockfile based on the Gemfile's name. This is super handy, as it keeps you from overwriting your project's Gemfile.lock if it's checked in, but you need to ignore this new lock file as well. If your gemfile is Foo.bar, look for Foo.bar.lock.
Finally, you can do something similar to Holger's suggestion in your custom Gemfile:
source "http://rubygems.org"
gem "fivemat"
instance_eval(File.read(File.dirname(__FILE__) + "/Gemfile"))
and you're good to go, as long as you remember to specify your Gemfile.
You can use something like this in your Gemfile:
gem 'foo' if ENV['ENABLE_FOO_GEM']
Then just set ENABLE_FOO_GEM in your environment.
export ENABLE_FOO_GEM=1
The gem will be disabled by default, but easily turned on (permanently) by anyone who wants to use it.
Add to .gitignore
Gemfile.local
Gemfile.local.lock
Add to the project a Gemfile.local.sample file with the following content:
# Include gems that are note meant to be part of the project but for development purposes
# That's why Gemfile.local and Gemfile.local.lock must be git-ignored
# To use these gems:
# 1. Create a "Gemfile.local" file (at same level of "Gemfile")
# 2. Prepend "BUNDLE_GEMFILE=Gemfile.local" before "bundle install" or "bundle exec rails c" and so forth.
eval_gemfile "./Gemfile"
group :development, :test do
# Suggested gems
gem "awesome_print", require:"ap"
gem "hirb"
gem "pry"
gem "pry-byebug"
gem "pry-rails"
gem "meta_request"
# My gems
gem "fivemat"
end
I believe the gem Devpack provides the functionality you are looking for.
The gem allows you to add a single gem to your Gemfile which will permit any developer to configure their own preferred set of development gems either for an individual project or globally by creating a .devpack file containing a list of gems.
(I am the author of this gem; I came across this post while developing it so thought it may be worth adding).
In case you still decide to do this (horrible idea):
You can add ruby code to your Gemfile to load a ~/.gemfile (or such) if it exists.
Something like:
eval(IO.read('~/.gemfile'), binding) if FileTest.exists?("~/.gemfile")

unpacking/freezing gems into a non-rails ruby app

I'm writing a non-Rails ruby application (gasp!) and would like to be able to include all the gem dependencies which the application requires in a vendor subdirectory. This would be similar to how http://gemsonrails.rubyforge.org/ works for Rails apps.
The goal here is to avoid the situation my team currently experiences when a new dependency is added. Every developer on my team has to install the gem manually, and then someone has to manually update each test and staging and production machine. If we can freeze the dependencies into the distributed application itself then a simple svn update (or git pull for those hipsters in the crowd) would be all that is needed.
UPDATE (New Solution):
Try Yehuda Katz's new bundler gem. gem install bundler then create a Gemfile with all your dependencies. See the documentation for more info.
Old Recommendation:
One easy way is to just manually unpack the gems into your vendor directory and add the lib path of the unpacked gems to the front of the $LOAD_PATH.
To unpack a gem:
$ cd vendor/gems
$ gem unpack active_support
Unpacked gem: '/path/to/myproject/vendor/gems/activesupport-2.3.2'
Just make sure you unpack all the necessary gems and their dependencies (using the correct versions).
To add all gems under vendor/gems to your $LOAD_PATH, try adding something like this to your application's initialization:
Dir.glob(File.join("vendor", "gems", "*", "lib")).each do |lib|
$LOAD_PATH.unshift(File.expand_path(lib))
end
Update: Sarah (in the comments) convinced me it might also be necessary to override the GEM_PATH. Here's one way to do that:
require 'rubygems'
gem_paths = [File.expand_path(File.join("vendor", "gems")), Gem.default_dir]
Gem.clear_paths
Gem.send :set_paths, gem_paths.join(":")
Another option is to look into Rip (Ruby’s Intelligent Packaging) for managing your dependencies. Rip looks really sweet, but it's still new.

Resources