Is it possible to bundle / install gems from a local cache? - ruby

I have bunch of gems on my computer that I want to use in a chef recipe.
I know it is possible to put them in a directory like /tmp/gems and just:
cd /tmp/gems
gem install *.gem
Is it possible to put all gems in one directory where I can install them with bundler without downloading them again?
cd /somedir/my_rails_project
bundle
I want to save bandwidth.

bundle install --local should be what you want. From the bundle-install manpage:
--local
Do not attempt to connect to rubygems.org, instead using just the
gems located in vendor/cache. Note that if a more appropriate
platform-specific gem exists on rubygems.org, this will bypass
the normal lookup.

You can add local directories to your Gemfile (example from the docs):
gem "nokogiri", :path => "~/sw/gems/nokogiri"
Alternatively, you can set up a local Git repository with the gems in it and write a Gemfile like this:
gem "gem1", :git => "file:///tmp/gems",
:branch => "gem1"

Use
bundle package
Locks and then caches the gems into ./vendor/cache.
The package command will copy the .gem files for your gems in the
bundle into ./vendor/cache. Afterward, when you run bundle install,
Bundler will use the gems in the cache in preference to the ones on
rubygems.org.
http://bundler.io/v1.6/bundle_package.html

You can use the BUNDLE_CACHE_PATH configuration key:
cache_path (BUNDLE_CACHE_PATH): The directory that bundler will place cached gems in when running bundle package, and that bundler will look in when installing gems. Defaults to vendor/bundle.
Source: https://bundler.io/v1.16/bundle_config.html#LIST-OF-AVAILABLE-KEYS
In GitLab CI, I defined this value in the environment of runners: "BUNDLE_CACHE_PATH=/cache-ci/bundle", this directory is mounted automatically in CI runners.
Then bundle install will install gems from the cache directory (once cache will be populated).

If you want to use a local cache for the purpose of speeding up bundle install on CI, for example when a docker container is used to run the tests, you could use --path. This will use gems in the given path unless they are not present, otherwise it will download them to that location.
This assumes the CI build can mount a persistent volume inside the docker container. So for example if the CI machine has a directory /var/cache/drone which can be mounted in the docker container as ./cache then you can do:
bundle install --without=development --quiet --path=cache

Related

How to run Bundle after installing?

I have done
gem install bundle
inside my directory that I have a git repository. But now when I try to run it with
bundle
run bundle
bundle install
it says:
Could not locate Gemfile.
How would I check that it has installed ?
It looks like it installed the gem correctly, however, you need to create a file named Gemfile for bundler to know what gems it should install. This file should be in your project's root. You can create one manually, or run bundle init to have a default one generated.
You can read more about the Gemfile in the docs

Bundler: using shared gem when it exists, rather than downloading from gem server

Say, I have Gemfile like following.
source "GEM_REPOSITORY"
gem 'gem_A'
# gem_A has no additional dependency
gem 'gem_B'
# gem_B depends on gem_B_1 and gem_B_2
When I run bundle install, I want Bundler to do the following.
If a gem already exists in "local system-wide gems", it copies the gem from local.
If a gem doesn't exist in local, it looks for GEM_REPOSITORY.
I looked for some related articles, and found some of likely-answers like
Ruby Bundler multiple sources in Gemfile
SOURCE PRIORITY
But none of the above looks like the answer for me.
Using source repository priority does't work. Because in the example above, if dependent gem (say, gem_B_1) exits in local but the target gem (gem_B) doesn't exist in local, it'll download both of above from the remote repository.
Are there any work around for doing this?
If not, don't you guys think it's necessary considering the cost of the implementation and the effect?
This is the current behavior.
When running gem install, directly or via bundle install, gem will first build a dependency graph with all the needed gems. If the gem is found locally it will use it, otherwise it will try to download it from the specified source.
If you want, try it yourself.
bundle gem gem_a
bundle gem gem_b
cd gem_a
vim gem_a.gemspec
add
spec.add_dependency 'multi_json', '~> 1.10.1'
or any dependency you want to the gem and run bundle install.
cd ../gem_b
vim Gemfile
and add
gem 'gem_a', path: '../gem_a'
then run
bundle install --verbose
you will see that the multi_json or whatever dependency of gem_a uses the local version and does not download anything.
This is of course also true for gems from remote sources.

How to put Ruby dependencies in local project folder

Is there a way to put a ruby gem dependencies inside the project root folder ( in vendor directory or something similar ) ?
Run bundle package to cache gems to ./vendor/cache. As the docs point out, bundler will still look for platform-specific gems on rubygems.org. If you have control over the platforms (e.g. same development and deployment platforms), then you can use bundle install --local in addition to bundle package. You may also want to consider a command like bundle install --path vendor/bundle in this case.
Just generate gemfile with proper app dependencies, place it into the root of your app, and use bunlder to keep depencencies up-to-date. Since by default all gem will be placed into system folders, I strongly recommend you to use rvm or rbenv utilities to use for purpose of gem keeping.

Using Bundler in deployment

Pretty fundamental question but I'm trying to understand how best to use Bundler in a deployment situation.
I'm working on a Sinatra application that has about 20 dependent gems. During development, I'm using RVM with a custom gemset for the application, and I run bundle install to update the gemset in accordance with the gemfile.
When it comes to deployment (manually for now, so I can understand how it all works before using a tool like capistrano), I need to do bundle install --development right? This downloads the gems and places them in vendor/bundle.
My question is what else do I need to do? I'm using Unicorn on the server - do I just bundle exec unicorn ... and everything just works? (i.e. bundler finds the vendor directory and uses the gems from there?)
Should unicorn be a vendored gem in the application or a separate 'system' gem on the server that all applications share?
You need --deployment key, not --development: http://gembundler.com/man/bundle-install.1.html#DEPLOYMENT-MODE
On first run bundler creates config in .bundle directory. You can check it by running bundle config or just cat .bundle/config in project's directory. So bundle exec unicorn is enough since bundler knows where gems are installed. On development machine you can also install gems to arbitrary directory using --path key. For more details see manpage for bundle install (link above or bundle help install).

How do I specify local .gem files in my Gemfile?

I have a couple of gem files which I install via gem install xx.gem. Can I tell Bundler to use them? Or do I have to specify the source path?
This isn't strictly an answer to your question about installing .gem packages, but you can specify all kinds of locations on a gem-by-gem basis by editing your Gemfile.
Specifying a :path attribute will install the gem from that path on your local machine.
gem "foreman", path: "/Users/pje/my_foreman_fork"
Alternately, specifying a :git attribute will install the gem from a remote git repository.
gem "foreman", git: "git://github.com/pje/foreman.git"
# ...or at a specific SHA-1 ref
gem "foreman", git: "git://github.com/pje/foreman.git", ref: "bf648a070c"
# ...or branch
gem "foreman", git: "git://github.com/pje/foreman.git", branch: "jruby"
# ...or tag
gem "foreman", git: "git://github.com/pje/foreman.git", tag: "v0.45.0"
(As #JHurrah mentioned in his comment.)
Seems bundler can't use .gem files out of the box. Pointing the :path to a directory containing .gem files doesn't work. Some people suggested to setup a local gem server (geminabox, stickler) for that purpose.
However, what I found to be much simpler is to use a local gem "server" from file system:
Just put your .gem files in a local directory, then use "gem generate_index" to make it a Gem repository
mkdir repo
mkdir repo/gems
cp *.gem repo/gems
cd repo
gem generate_index
Finally point bundler to this location by adding the following line to your Gemfile
source "file://path/to/repo"
If you update the gems in the repository, make sure to regenerate the index.
I would unpack your gem in the application vendor folder
gem unpack your.gem --target /path_to_app/vendor/gems/
Then add the path on the Gemfile to link unpacked gem.
gem 'your', '2.0.1', :path => 'vendor/gems/your'
By default Bundler will check your system first and if it can't find a gem it will use the sources specified in your Gemfile.
You can force bundler to use the gems you deploy using "bundle package" and "bundle install --local"
On your development machine:
bundle install
(Installs required gems and makes Gemfile.lock)
bundle package
(Caches the gems in vendor/cache)
On the server:
bundle install --local
(--local means "use the gems from vendor/cache")
Adding .gem to vendor/cache seems to work. No options required in Gemfile.
I found it easiest to run my own gem server using geminabox
See these simple instructions.

Resources