Parsing a Gemfile.lock with Bundler - ruby

Basically, I'm trying to build a gem that does some form of test failure when certain dependencies are outdated. In the testing framework I can easily stub the crap out of Gem so that Gem.loaded_specs("foo") returns the spec for the gem foo. What I'm looking for is a way to provide a fixture Gemfile and then parse the lock file.
When I use Gem.loaded_specs it somehow magically knows which Gemfile to use, how do I feed it a different gemfile (ie: my fixture)?
I know somebody mentioned "use bundler" and that makes sense but in my code for the gem I do this:
gem_here = Gem.loaded_specs[gem_name]
gem_here.nil? ? :not_in_bundle : gem_here.version.to_s
I need to make this work with my fixture Gemfile and not the current projects gemfile.
Does this make any sense? Sorry if this is vague.
Note: I'm not trying to do this via the CLI. I'd like to use programmatic (ie: ruby api) methods if I can.
Edit:
I'm going with this kind of source now:
gem_here = Bundler.load.specs.detect do |specs|
specs.name == gem_name
end
gem_here.nil? ? :not_in_bundle : gem_here.version.to_s

Parsing yourself isn’t desirable as Bundler does the heavy-lifting (parsing, dependency resolution).
Bundler::LockfileParser.new(Bundler.read_file(Bundler.default_lockfile))
Then, use gemspecs and the lockfile to visit all runtime and/or development dependencies. Runtime/development deps for specific gems are available via a (currently undocumented, needs contrib) RubyGems JSON API https://api.rubygems.org/api/v2/rubygems/rails/versions/5.0.0.1.json
Note: Bundler 2.0 Gemfile.lock -> gems.locked

Related

Creating a gem with Bundler

I'm trying to create a gem with Bundler, following this guide: http://rakeroutes.com/blog/lets-write-a-gem-part-one/. In it, it says:
I incorrectly thought after taking my first look through the gemspec
that I would need to add more require statements as I developed my
gem. That isn’t the case: the files just need to be in git.
I am trying to clean up some of my old gems to use this convention, but when I install my gem, the classes from the other files are not available. I have a couple directories nested under my /lib dir, but I wouldnt think that would be an issue. Is there anything simple to overlook that would prevent my other files from being required? Any help would be appreciated.
In the link, when he says he doesn't need to add a lot of "require" statements, he must mean adding files to the s.files, s.executables, and s.test_files arrays--these determine what files get packaged up into the gem and what files get ignored. As you can see from the gem spec, whatever's tracked by git in certain directories is going to be included in the packaged gem.
Ruby's require is a different story. Standard require rules still apply.
Ruby's gem system works by adding a bunch of different places for Ruby to look for "foo.rb" when you run require "foo". If "lib" is your only require path for your gem, when you require "my_gem" Ruby is only going to run the code in lib/my_gem.rb. If lib/my_gem.rb doesn't require any other files in your gem, then Ruby hasn't seen them and so you'll get undefined constant errors when you try to use the classes from those files.
For examples, you might take a look at two simple gems I've written; both were started with bundle gem: HashToHiddenFields and SimpleStats. In both gems, main Ruby file in lib/ requires everything that needs to be loaded for the gem to work correctly. For example, hash_to_hidden_fields.rb requires action_view/helpers/hash_to_hidden_fields so that the ActionView::Helpers::HashToHiddenFields constant+module exists so we can include it into ActionView::Base.
Hope that answers the question. I know Ruby requiring was pretty fuzzy to me for a while.

How to customize Gemfile per developer?

There is a common pattern:
there are many developers working on one project and the Gemfile(.lock) is shared via SCM. But what if some developers want to use different tools for testing and development? How to do it?
The problem is, that when you put conditional sections to your Gemfile, also the Gemfile.lock will be different for each developer and therefor you'll get conflict each time you commit to SCM.
Is there some simple, widely acknowledged solution?
I like to have this in my Gemfile:
local_gemfile = File.dirname(__FILE__) + "/Gemfile.local"
if File.file?(local_gemfile)
require local_gemfile
end
I also have Gemfile.local and Gemfile.lock in gitignore. I know I'm not "supposed to", but I don't think the caveats (such as the ones you mention in your question) are worth it.
UPDATE for Bundler 1.0.10 as of March 3, 2011
local_gemfile = File.dirname(__FILE__) + "/Gemfile.local.rb"
if File.file?(local_gemfile)
self.instance_eval(Bundler.read_file(local_gemfile))
end
I had to use this with Rails 3 and Bundler 1.0.10.
If you check in something that depends on a gem that gem should be in the gemfile. If the code in the repository does not depend on a gem, there's no need to have it in the gemfile. So, unless your developers don't check in their tests (which would be weird) you would need all the test's dependencies if you want to run the whole tests suite anyway.
If the gems aren't necessary to run the app or its tests the gems don't need to be in the gemfile. Just have each developer create a gemset (I assume you're using RVM, if you don't you should) for the app and install whatever they need there, and then just add what the app needs to run to the gemfile.
You can use Bundler's without flag to exclude groups.
If you have the following Gemfile
group :jakubs_testing_tools do
gem "rspec"
gem "faker"
end
You can exclude them with bundle install
$ bundle install --without jakubs_testing_tools
http://gembundler.com/groups.html
It won't help you right now, but there's been an open feature request for Bundler to add support for a Gemfile.local for ages. It is planned for somewhere in the 1.x series, so stay tuned.
If your main problem is developer-specific IRB gems, there are a couple of workarounds in the issue's comments.
I suppose the install_if method (added recently) solves the problem:
install_if -> { `whoami`.strip == 'jakub' } do
gem "pry-rails"
end
See http://bundler.io/v1.14/man/gemfile.5.html#INSTALL_IF
Each developer can create their own branch - Bundler works fine with different branches having different Gemfile contents. It's a good idea for developers to prefix branch names with their initials, to avoid confusion or collisions.
(basically the same idea as in August Lilleaas's comment: gitignore)
Put the default/minimal Gemfile in SCM and then have developers change it on their systems and never commit. Have them add it to their inactive changeset in their SVN client (if they use one), other SCMs should have something similar.
This is how we do this in my company - works really well :)

Getting started with gems and jeweler

With Jeweler I created a gem folder structure with ease.
However, I still have some questions:
Why are params like --gemcutter and --rubyforge still available for Jeweler. Aren't these replaced by RubyGems? Do I have to specify anything to create a gem for RubyGems?
In the Rakefile I have information about the gem, and when I run "rake install" it created a gemspec. Why is the same information in two places?
What is a manifest? Just read about it, haven't seen such file.
How do I make my gem callable from the shell once I have installed it, like rails. Cause right now it's just accessible through a Ruby script using require.
Should I use "jeweler release" or "gem push" to push my gem to RubyGems.org?
I have to specify "handle" when signing up in RubyGems. What is that?
Thanks.
jeweler was created before RubyGems became what it is, so it still reflects the split. I'm not sure when jeweler was last updated, either. (I think it also still recognizes building gems on Github, which is now disabled.)
I'm not sure I follow what you're saying. The specification in the Rakefile details what the spec that gets written should look like. The spec that gets written details what should be installed and how, I believe.
A manifest is a list of all the files that your gem should ship with. Not everyone uses one. See the hoe documentation for some pro-manifest discussion.
Many Ruby gems are only libraries. If you want yours to also have a program like jeweler or rake or rails that you can call, you have to write the callable program, put it in bin in your gem's layout and specify (in your gemspec) that it should be packaged and installed. See the Gem::Specification reference under files and executable.
Not sure. Consult both jeweler's docs and the docs for RubyGems.
You can give an email address or use a name (a 'handle', like I use Telemachus here), which is all they mean by 'handle'.
For the record, if you are just learning how to write gems, you do not need to upload your first attempts using RubyGems or anything like it. You can simply install the gem on your machine only.

What is the modern way to structure a ruby gem?

Has much changed with the release of Bundler? Is there a template that can be used as a base? What are the best practices?
Some posts that I have found useful:
http://chneukirchen.github.com/rps/
http://tomayko.com/writings/require-rubygems-antipattern
http://yehudakatz.com/2009/07/24/rubygems-good-practice/
http://weblog.rubyonrails.org/2009/9/1/gem-packaging-best-practices
Edit (2012-01-10): An excellent all-around guide to gem best practices is RubyGems Guides. I would highly recommend starting here now.
To summarize the key points:
Use the basic lib/gem.rb and lib/gem/ structure for code.
Put any executables in bin, any data files in data and tests in test or spec.
Don't require or depend upon files outside of the load path. (VERSION files often seem to live in odd places in gems.)
Do not require 'rubygems'.
Do not tamper with the $LOAD_PATH.
If you find yourself writing require File.join(__FILE__, 'foo', 'bar'), you're doing it wrong.
The simplest way it's to use bundler:
bundle gem <gem_name>
You may even use it in an existing project from the parent directory.
When writing fat (binary) gems the structure is usually this:
lib/1.8/binary.so
lib/1.9/binary.so
lib/my_gem.rb (this file simply chooses which binary.so to load depending on ruby version)
And for native extensions:
lib/ext/my_gem/my_sources.*
lib/my_gem.rb
I also usually put a version.rb file here:
lib/my_gem/version.rb
and it simply contains something like:
module MyGem
VERSION = "0.1.0"
end
Also, IMO, don't put any .rb files except the file you want people to use to load the gem, in the lib/ directory. Instead put all auxiliary files in lib/my_gem/
This rubygems guide provides information about the structure of a gem and then goes into detail about what should be included in your gemspec
You may find it easier to use bundler to create the folder structure of the gem for you:
bundle gem <gem_name>
my_gem$ bundle gem my_gem
create my_gem/Gemfile
create my_gem/Rakefile
create my_gem/LICENSE.txt
create my_gem/README.md
create my_gem/.gitignore
create my_gem/my_gem.gemspec
create my_gem/lib/my_gem.rb
create my_gem/lib/my_gem/version.rb
Initializing git repo in /Users/keith/projects/my_gem/my_gem
Telemachus's advice is good. If you follow it your gem will be setup to play nicely with bundler.
You might also try using jeweler. It's a gem that generates skeletons for gems. The default skeleton that it spits out complies with all of the conventions Telemachus mentioned and it will also do some nice things like add your favorite test framework or create a GitHub repository.

How to develop a gem in staging environment?

I am trying to hack through a forked gem (buildr). As such I cloned it from github and began to butcher the code. The official gem is installed on my system (under /usr/lib/ruby.../gems/buildr...). There is an executable which I need to use in my dev process - buildr.
Now I want the buildr executable and the library to point to my forked repo and not the default gem installation. This would be for this gem only. As such, the changes I make against the forked repo is usable directly for testing and so forth.
I would guess I need to load my library prior to the system gem loading. Can somebody recommend the best way to do so?
I did something similar for work when the Spreadsheet gem broke backward compatibility. I put the previous versions code in it's own module and just renamed the gem my-spreadsheet and installed that (I really wanted some of the features of the new gem but I also didn't want to rewrite all my previous code at that point).
If it's just a binary you want to override you could always do some PATH magic, setting the directory of your binary first and thus make sure you always override. But personally I'd prefer making my own copy with a new name and installing that.
you could bump the version in the gemspec for your fork. Then when you install your version of the gem, it will use your (newer) version by default.
change buildr.gemspec
#...
spec.version = '1.3.4.dev'
#...
Then
$ gem build buildr.gemspec
$ sudo gem install buildr-1.3.4.dev.gem
and it should work.

Resources