Why is bundler using the wrong gemspec file? - ruby

I've got a custom gem that has been working just fine with regards to bundling, building, distributing, & implementing. The gem is the core of a framework from which other gems are derived. Since most derived gems will have the same basic structure, I want to include a Ruby script in the bin path of the gem that can be used to basically copy files from a template folder into a new folder where the user will develop their own gem.
The problem I'm having is that the template folder has a gemspec file named $name$.gemspec with similarly named classes/modules in the file (e.g.: module $Name$), where the $name$ gets replaced with a name provided by the user.
Unfortunately, when I run bundle install from my gem's top-most path, I get an error:
There was a SyntaxError while evaluating $name$.gemspec:
C:/my_gem/template/$name$.gemspec:8: syntax error, unexpected tGVAR
gem.version = MyGem::$Name$::VERSION
It looks like Bundler is using the wrong Gemfile, even if I explicitly pass the Gemfile or path via one of the following:
bundle install --gemfile=Gemfile
bundle install --path=C:\my_gem
I also tried updating the gemspec line of my Gemfile to no avail:
gemspec name: 'my_gem'
Lastly, I've ensured that the template folder isn't even included in my_gem.gemspec, but that doesn't seem to matter:
gem.files = Dir.glob("lib/**/*") + %w(LICENSE.txt README.md)
Does anyone know why Bundler is trying to read the ./template/$name$.gemspec instead of ./my_gem.gemspec?

Inspecting the Bundler source, I may have spotted the culprit in lib/bundler/source/path.rb. There's GLOB used to find gemspecs in load_spec_files. The default glob is "{,*,*/*}.gemspec". This will find *.gemspec in the root directory of your gem or any directory one descendant from root (which will include your template dir).
If this is indeed the culprit, you could work around this by placing your template directory deeper in your gem's dir hierarchy or changing the name of the template file so it doesn't end in .gemspec. The Bundler::Source::Pathobject looks like it can take a different glob at initilization but I haven't dug deep enough to see if there's a viable way to specify this alternative glob in bundle execution via config or cmdline options.

Related

What is the built gemspec file (with version) for?

When I run the command
$: gem build example_gem.gemspec
I get a file called example_gem-0.0.0.gem. What is in this file and what is it for?
Does this file play a part with how gems get included into Ruby code that requires them once the gem is already installed on the machine?
I noticed that when I go into my INSTALLATION_DIRECTORY, I can see the gems in folders with names matching matching the format of this versioned gem file, but inside the directory, I only see the other library code, gemspec file, etc.
Does this mean it is for uploading/downloading/installing only?
The .gem file is, well, the Gem. It contains the Gemspec (more precisely, a Marshal serialized version of the object graph of the Gemspec, rather than the executable .gemspec while which generates that object graph) as well as a compressed archive of all the files that are part of that gem (listed in the Gemspec). It also contains a cryptographically secure checksum and an optional cryptographic signature.
When you install a .gem, RubyGems checks the cryptographic checksum to make sure the Gem was not tampered with, checks the optional signature to make sure the Gem was created by who you think it was, reads the Gemspec to figure out what to do with the Gem, and unpacks it into the $GEM_HOME directory.
Kernel#require simply goes through the search path until it finds a file matching the name you provided and runs the file. It knows nothing about Gems. (It does know how to find the unpacked Gem directory, though.)
[Note: this is somewhat simplified. Kernel#require may have implementation-specific features, for example, YARV's Kernel#require also knows how to load dynamic libraries (e.g. .dlls, .sos, .dylibs, depending on the OS), JRuby's Kernel#require also knows how to load .jars, and so on.]

Ruby: require works in gem, fails when running from source

Try creating a gem based on bundler's official guide on developing a Ruby gem.
Running bundle gem foodie will create a structure and generate files in the lib directory:
foodie
version.rb
foodie.rb
foodie.rb reads
require "foodie/version"
module Foodie
# Your code goes here...
end
Running ruby lib/foodie.rb (or also from different directories) will result in
C:/Ruby23-x64/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require': cannot load such file -- foodie/versio
n (LoadError)
from C:/Ruby23-x64/lib/ruby/2.3.0/rubygems/core_ext/kernel_require.rb:55:in `require'
from foodie.rb:1:in `<main>'
On the other hand installing the gem via rake install and then requiring the gem works just fine.
It works from source if require "foodie/version" is changed to require_relative "foodie/version" in foodie.rb. As I understand
require works based on modules
require_relative works based on directory structure
To me the latter looks like a hack. It'd no longer make sense to structure your code via modules as it wouldn't be enforced (maybe it'd still make sense but you could make mistakes and never notice).
My questions are:
Is it possible to test a gem from source without installing it while following the bundler convention (using require instead of require_relative)?
Why does the gem work after installed?
Is there any best practice for the usage of require, require_relative, modules, files and general structure?
Thank you.
You need to add your lib dir to Ruby’s load path. The load path is a list of directories that Ruby searches for files in when you call require. Rubygems also manages the load path when you are using gems, which is why your code works when installed as a gem.
You say “as I understand ... require works based on modules”, this is not correct. require works with files, it’s just convention that a class or module is defined in a file with a matching name, e.g. MyModule might be in my_module.rb.
There are a few ways to add a dir to the load path. From the command line you can use the -I option:
$ ruby -I lib lib/foodie.rb
If you wanted to avoid typing -I lib you could use the RUBYLIB environment variable. Ruby adds the contents of this to the load path:
$ export RUBYLIB=lib
$ ruby lib/foodie.rb
(On Windows I think you will need to use set rather than export.)
You can also manipulate the load path from withing the program itself. It is stored in the global variable $LOAD_PATH, aliased as :$. This is how Rubygems and Bundler manage your gems.

How to I load a gem from source?

I have git cloned a repo from Github, now I want to experiment with it, as in I want to poke around the code and mess with it. I've created a file test.rb that should load this gem, but I want to load my locally checked out version, what's the right way to do this?
Right now I'm just using a bunch of "require_relative 'the_gem_name/lib/file'", which feels wrong.
When you require 'foo' Ruby checks all the directories in the load path for a file foo.rb and loads the first one it finds. If no file named foo.rb is found, and you’re not using Rubygems, a LoadError is raised.
If you are using Rubygems (which is likely given that it is included in Ruby 1.9+), then instead of immediately raising a LoadError all the installed Gems are searched to see if one contains a file foo.rb. If such a Gem is found, then it is added to the load path and the file is loaded.
You can manipulate the load path yourself if you want to ensure a particular version of a library is used. Normally this isn’t something that’s recommended, but this is the kind of situation that you’d want to do it.
There are two ways of adding directories to the load path. First you can do it in the actual code, using the $LOAD_PATH (or $:) global variable:
$LOAD_PATH.unshift '/path/to/the/gems/lib/'
require 'the_gem'
Note that you normally want to add the lib dir of the gem, not the top level dir of the gem (actually this can vary depending on the actual Gem, and it’s possible to need to add more than one dir, but lib is the norm).
The other way is to use the -I command line switch to the ruby executable:
$ ruby -I/path/to/the/gems/lib/ test.rb
This way might be a bit cleaner, as normally you don’t want to be messing with the load path from inside your code, but if you’re just testing the library it probably doesn’t matter much.
Following apneadiving's suggestion in the comments, I created a Gemfile and added this line
source "http://rubygems.org"
gem 'gem_name', path: '~/path/to/gem/source/folder'
Then bundle install, and bundle exec ruby test.rb and it worked.

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.

Rubygem Executable $LOAD_PATH Issues

I'm writing an IDE in Ruby, and I'm stumped on how to get all my files to get "required" when I run the program on the command line, AND when its installed as a Rubygem.
My Rubygem has an executable file named "vr" in it. I need to make this "vr" executable file "require" all the other files from my project.
When I'm developing, its easy to require all my project's files. I simply "require" a relative path to them like this:
require_all Dir.glob(File.expand_path(File.dirname(__FILE__)) + "/../bin/**/*.rb")
The require_all gem will work perfectly. However, I get a big problem when I install this program as a rubygem. When my "vr" executable is installed by rubygems, it copies the "vr" executable to a special directory:
/home/eric/.rvm/gems/ruby-1.9.3-p125/bin
This directory is totally separated from my project's root folder. And so all my project's files are no longer found by the "require" statement.
Rubygems makes this directory for my gem's root:
/home/eric/.rvm/gems/ruby-1.9.3-p125/visualruby-0.0.55
I need to be able to "require" all the files from that directory into my project.
My solution so far, is to make a second file called "visualruby.rb" that resides in my project's lib folder. It has the require_all statement in it to require all the project files. Then I just have to link the executable to it by adding this code to my "vr" executable file:
base_file = File.dirname(__FILE__) + '/lib/visualruby.rb'
if File.file?(base_file)
require base_file #load from project
else
require 'visualruby.rb' #load from gem
end
It is necessary to check if there's a file named "visualruby.rb" relative to the current file because when I'm developing, it will always find the installed gem's version of "visualruby.rb" So when I make a change to a file, it has no effect. I have to force it to load the version from my development project for changes to work.
Also, my IDE creates projects from scratch, so it would be nice to know the general solution to this. I'd like to have a consistent project file system for all projects, but I'm not sure that's possible. I had the general solution of making a file called "requires.rb" for all projects, but I don't think it will work because every project will have the same filename added to the $LOAD_PATH.
Please help me understand how I can make a consistent file structure where I can develop, and make rubygems.
I found the answer to my own question:
The problem was that I was installing my rubygems using the rubygems API:
Gem::Installer.new(file_name)
This created syslinks that messed up my paths. There is an option to make wrappers instead of syslinks and that seems to be the standard way to install:
Gem::Installer.new(file_name, :wrappers => true)
Now the a wrapper is copied to my gem's bin directory and it uses the correct path. Now I can have a universal file that can be made into a gem. And everything runs the same in development and in the gem.
A happy ending...

Resources