How to add conditional Gemspec dependencies - ruby

I have made a Gem that optionally logs with Sentry or Airbrake, depending on the ENV variables. I have these in my Gemspec but I only need one or the other:
spec.add_runtime_dependency "sentry-raven"
spec.add_runtime_dependency "airbrake"
Is there a way to only include the one that the end user needs, based on ENV variables?

You may be best off leaving off both dependencies and adding to your README that a user needs to add the dependency to their own Gemfile. I've seen some gems check to see what is defined, raising an error if they don't find anything, as well as allowing the user to configure in their application which tool to use.

No, this is not possible for the simple reason that the Gemspec is executed on your computer, not the user's computer. It is executed to build the Gem, not install it.
RubyGems does not have a sophisticated dependency system like e.g. dpkg/APT has: a Gem either depends on another Gem or it doesn't.

Related

Parsing a Gemfile.lock with Bundler

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

Creating Ruby gems with same name executables?

I want to have a Ruby Gem that will have the same executable as another Gem.
When called with command args it will either do something, or pass the command on to the other Gem.
The first problem I have is that it isn't able to run two same named executables. I get this error:
Bundler is using a binstub that was created for a different gem. This is deprecated, in future versions you may need to bundle binstub yourgem to work around a system/bundle conflict.
How can I have Gems with the same named executables and ensure that the target one executes?
You cannot rely on Bundler or Rubygems to manage this for you. All it does it copy an executable that you specified in your gem spec to its bin/ directory.
The first problem you'll have is that the executable that runs may be dependent on the order in which the gems were installed which you can't guarantee.
Another problem that you'll have is that you cannot execute code on gem installation so you will be unable to run code that would try to automate this set up for people who install your gem.
I believe your gem should provide a non-conflicting executable. You can then supply post install instructions in your gem spec that are displayed to a user installing the gem, in the README, in a blog post, etc. you can tell the user that they need to set up an alias that points to your executable. In all shells that I'm aware of aliases will be executed before filesystem executables.
For the times when people want to bypass your alias and execute the original executable you can tell people to escape the command, e.g. \original-gem. That bypasses alias and function lookup in most shells and will allow users to have your super awesome version as the default (thru the alias) and a way to easily access the original.

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.

Resources