overriding bundler's release, but without putting it in the gem - ruby

I'm using bundler to manage my gem's dependencies, and I'm using geminabox to host my gems once they're ready. Using bundler's rake release is bad news, because my gems will go to rubygems, not to geminabox, however the additional functionality (push a tag etc) from that task are really helpful.
I wrote a gem which adds the ability to rake release geminabox and rake release rubygems, but now my library (which has nothing to do with geminabox) depends on this bundler/geminabox thing, which in turn depends on geminabox, and futher on sinatra, rack... all of a sudden my development requirements are huge and are only specific to my development situation. Some of these gems will be released publicly and these geminabox development dependencies will not be necessary to anyone else.
I realise that injecting a gem into a bundler gemset is specifically what bundler is intended to prevent, so what would you recommend as a way to have my own custom release functionality which is not tied to the gem itself?

I've redefined the default Bundler release task in my gem's Rakefiles along the lines below. You're right that you don't want to suck in Sinatra and a whole bloated stack.
You are left with 2 choices...
write custom rake tasks that do everything you need from scratch
redefine Bundler's default release task, since that's the only bit you want to behave differently.
Rakefile
require "bundler/gem_tasks"
Rake::TaskManager.class_eval do
def remove_task(task_name)
#tasks.delete(task_name.to_s)
end
end
def remove_task(task_name)
Rake.application.remove_task(task_name)
end
remove_task :release # So we don't publish to rubygems.org
desc "release to geminabox"
task :release => [:build] do |t|
system "gem inabox"
end

Related

Bundler's rake release with geminabox?

Is there a way to configure bundler so that when I do rake release it would submit the gem to my own gem server (a gem in a box instance) rather than to rubygems?
Ideally this configuration would be something I can omit from my git repository.
Rubygems is actually hard-coded into bundler and I've found only one way around it.
The following monkeypatch should get you what you want:
module Bundler
class GemHelper
protected
def rubygem_push(path)
if Pathname.new("~/.gem/nexus").expand_path.exist?
sh("gem nexus '#{path}'")
Bundler.ui.confirm "Pushed #{name} #{version} to https://<your-url-here>/."
else
raise "Your Nexus credentials aren't set. Run `gem nexus #{path}` to push your gem and set credentials."
end
end
end
end
The above is for Nexus instead of Geminabox, but the concept should apply to either.
As far as omitting it from git, I'm afraid we're out of luck. However, you can share this appropriately between projects so it will only have to be checked into one place rather than many. Hope this helps!
i made a dependency free gem for this which imitates the geminabox http post request and overwrites bundlers rake release with rake release:inabox
https://github.com/dfherr/geminabox-release
I managed to change the task which uploads the gem to rubygems, that's less intrusive then the solution provided by JohnIV, though the concept is the same.
Rake::Task['release:rubygem_push'].clear
namespace :release do
task :rubygem_push do
version = ModuleName::VERSION
name = 'module_name'
cmd = "gem nexus pkg/#{name}-#{version}.gem"
puts `#{cmd} 2>&1`
end
end
Add 'bundler_geminabox' to your Gemfile:
group :development do
gem 'bundler_geminabox'
end
Then, in your rakefile, instead of requiring 'bundler/gem_tasks':
require 'bundler_geminabox/gem_tasks'
You don't need to add any tasks to the rakefile; you will automatically get rake build, rake install, and rake release, the last of which uploads to the server listed in ~/.gem/geminabox. Otherwise, the behavior is the same as the equivalent tasks provided by bundler/gem_tasks.
Gem on Github: https://github.com/joshkrueger/bundler_geminabox

build a ruby gem and conditionally specify dependencies

I am working on a gem that has needs to set dependencies conditionally when the gem is installed. I've done some digging around
and it looks like i'm not alone in this need.
Rubygems: How do I add platform-specific dependency?
this is a long thread
http://www.ruby-forum.com/topic/957999
The only way I can see to add dependencies to a gem is to use add_dependency method within a Gem::Specifiction block in a .gemspec file
Gem::Specification.new do |s|
# ... standard setup stuff
# conditionally set dependencies
s.add_dependency "rb-inotify", "~> 0.8.8" if RUBY_PLATFORM =~ /linux/i
s.add_dependency "rb-fsevent", "~> 0.4.3.1" if RUBY_PLATFORM =~ /darwin/i
s.add_dependency "rb-fchange", "~> 0.0.5" if RUBY_PLATFORM =~ /mswin|mingw/i
end
Based on all of the docs and threads I found on the net, I would have expected that if you install the gem on
Linux, then, rb-inotify would be a dependency and auto-installed
Mac - rb-fsevent would be installed
Windows - rb-fchange would be installed
However, it seems that is not the case. The "if" statements within the block are evaluated at the time the gem is built and packaged. Therefore,
if you build and package the gem on Linux, then, rb-inotify is added as a dependency, Mac, then, rb-fsevent, Windows - rb-fchange.
Still needing a solution, I dug around in the rubygems code and it seems the following is a broad stoke of what happens.
build all of your code for your gem: foo.gem
create a foo.gemspec file
build, package, and release the gem to a gem server such as rubygems.org
let everyone know
developers install it locally via: gem install foo
the foo.gem file is downloaded, unpacked, and installed. all dependencies are installed as well.
everything should be set and we can beging using the gem.
It seems that when the gem is built and released the foo.gemspec file is loaded and the Gem::Specification block is evaluated and converted to YAML, compressed as
metadata.gz, and included in foo.gem. The ruby code is compressed into data.tar.gz and included as well. When the gem is installed on the local developer machine,
the YAML is extracted from metadata.gz and converted back into a Gem::Specification block, however, it is not converted back to the original block.
instead, you will see something like the following:
Gem::Specification.new do |s|
if s.respond_to? :specification_version then
s.specification_version = 3
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
s.add_runtime_dependency(%q<rb-inotify>, ["~> 0.8.8"])
else
s.add_dependency(%q<rb-inotify>, ["~> 0.8.8"])
end
else
s.add_dependency(%q<rb-inotify>, ["~> 0.8.8"])
end
end
Ok. So, I have a bird's eye view of the process, however, that does not change my desire to build a single gem and conditionally specify dependencies for a range of OS targets.
If anyone has a solution other than building multiple .gemspec files for each target OS... I'm all ears!!
I have also stumbled upon this problem in the past. The only workaround I could find was to create a Rake task for installing the dependencies. Of course, at that stage, you might just want to let the user figure out on his own which gem he is missing based on the error message he is receiving. In my case, there were several platform-dependent dependencies to be installed, so that wasn't an option.
Rakefile:
task :install do |t|
require './lib/library/installer'
Library::Installer.install
end
Installer:
module Library::Installer
require 'rubygems/dependency_installer'
def self.install
installer = Gem::DependencyInstaller.new
dependency = case RUBY_PLATFORM
when /darwin/i then ["rb-fsevent", "~> 0.4.3.1"]
when /linux/i then ["rb-inotify", "~> 0.8.8"]
when /mswin|mingw/i then ["rb-fchange", "~> 0.0.5"]
end
installer.install(*dependency)
end
Then, the user can use rake install to get install appropriate dependencies.
Conditional dependency install (not just based on platform, but based on user input, for example) is cruelly missing to RubyGems. Let's hope it'll get implemented in the future!
i have never done this myself, but there are some gems that are available in platform specific versions: http://rubygems.org/gems/libv8/versions
from what i understand it's just a naming thing, which can be configured by setting the platform option of your gemspec. have a look at the doc: http://guides.rubygems.org/specification-reference/#platform=
I have looked into this as well and have come to the conclusion that is not possible by design. Having a single 'mega gem' for all platforms causes the problem of not knowing if a platform is supported until the gem is downloaded and installed. A Gem would have to be smart enough to determine what is correct way to install depending on the platform. If a platform is not supported at all, the gem may fail horribly, opening a big can of worms. There use to be a callback after a gem was installed that was removed for the same reason, no magic to get a gem to install correctly. Some people have hacked around this using mkmf, but I suggest following the worn path of a gem per platform as the better solution.
Based on this, in a project that builds a gem for ruby and jruby, I have to manually create each gem and upload them to RubyGem. Using Jeweler this is as simple as specifing the Gemfile, but I have to rebuild the gem spec each time I package a gem. Fairly trivial when supporting only 2 platforms, but the build process is straight forward enough that it could be automated to provide support multiple platform gems.

Where do I have to do my Bundler setup?

If I'm developing a gem using Bundler and RSpec for testing. Where do I do my Bundler.setup? Let's assume my gem is called fancy-gem and my directory setup is similar to the following:
Gemfile
Gemfile.lock
lib/
fancy-gem.rb
Rakefile
README
spec/
...
Should I execute Bundler.setup in my 'fancy-gem.rb' or does this cause problems with other gems which might use bundler? I'm thinking, when I'm not doing this, then there is no way to guarantee that the right version of the third party libraries I'm requiring is loaded.
I already asked, if I need to add Bundler itself to the Gemfile. The answer was no, but now I'm not so sure, because if I do execute Bundler.setup somewhere then Bundler actually is a dependency of my Gem and should be installed along with my Gem when it is downloaded from rubygems.org
In my opinion you should neither depend on bundler, nor use it in your gem. The way I'd do it is simply require your gem's dependencies in lib/fancy-gem.rb (almost every gem only has a handful of runtime dependencies, so this should not be too much of a hassle) and I'd call Bundler.setup only in the development files (like spec_helper.rb or Rakefile). This way you don't screw with applications that use your gem and still get all the convenience of automatic dependency management when developing your gem.

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.

How to quickly initialize ruby project development environment?

How to specify gem dependencies in a way that user with only ruby, rake and rubygems installed could issue a single rake command to install all the dependencies required? Is it possible to use the same dependency specification when building gem with GemBuildTask?
It's actually pretty easy to set up a rake task that installs a bunch of gems:
task :install_gems do
require "rubygems"
require "rubygems/dependency_installer"
installer = Gem::DependencyInstaller.new
[["rack"], ["merb-core", "1.0.12"]].each do |args|
installer.install(*args)
end
end
Of course, you could extract this into a method and write a prettier way to specify your dependencies, but this should work great.
I think currently you'd have to write a custom rake task that talked to the Gem library.
It's possible that rip, the (very) new kid on the block, will make it all easier, but it's very early days.
But someone else may have a better way...
If your app is packaged as a gem, you could add the dependencies to the gemspec and rubygems will attempt to install them for you when you install the gem.
There are a bunch of ways to make a gem out of some ruby code. Recently I have taken to using jeweler.
With it, you can install a project as a gem by running rake install. There are some instructions on how to do dependencies on its github wiki.

Resources