Install Ruby gem with Native Extension - ruby

I am creating a new gem that uses FFI to create a ruby binding to webview C library
The gem structure is as follows:
.
├── ...
├── Rakefile
├── ext
│   └── webview
│   ├── extconf.rb
│   ├── webview.cc
│   └── webview.h
├── lib
│   ├── webview
│   │   └── version.rb
│   ├── webview.rb
│   └── webview.so
├── ...
└── webview.gemspec
The Rakefile is as follows:
require "bundler/gem_tasks"
require "rubocop/rake_task"
require "rake/extensiontask"
RuboCop::RakeTask.new
task default: :rubocop
Rake::ExtensionTask.new "webview"
The Gemspec is as follows:
# frozen_string_literal: true
require_relative "lib/webview/version"
Gem::Specification.new do |spec|
...
# Specify which files should be added to the gem when it is released.
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
spec.files = Dir.chdir(File.expand_path(__dir__)) do
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
end
spec.bindir = "exe"
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]
spec.extensions = ["ext/webview/extconf.rb"]
# Uncomment to register a new dependency of your gem
spec.add_dependency "ffi"
spec.add_dependency "rake-compiler"
end
When I run rake compile it compiles the library and adds webview.so to the lib/ dir.
But when I run rake install:local the webview.so is not in place!
What's wrong?

I needed to add the ext folder to git using git add ext/ this way git ls-files was able to capture it and includes the file in the installation process.
Also, I followed the convention of rubygems by moving webview.rb into lib/webview/ and created a webview.rb in lib/ that contains only:
require_relative "webview/webview"
Also, I added this block to Rake::ExtensionTask in Rakefile:
Rake::ExtensionTask.new "webview" do |ext|
ext.lib_dir = "lib/webview"
end
in order to move the shared object inside lib/webview/
Last thing, I did is to call ffi_lib with absolute path not relative path like this:
ffi_lib File.dirname(__FILE__) + '/webview.so'
And Voila! it works

Related

What is the recommended folder structure for a Ruby command-line app with tests?

I'm new to the Ruby development ecosystem and learning Rails in my spare time. However, I would like to build a command-line app from scratch with unit tests as a part of my learning.
What is the recommended structure for a Ruby command-line project? The one I came up with is
/test
- person_test.rb
/src
- person.rb
main.rb
Gemfile
Gemfile.lock
Thanks
Unit Tests for Gems
If you're using MiniTest or similar, the expected place within gem foo would be foo/test. If you're using RSpec or minitest/spec, then you'd use foo/spec instead.
When you generate a new gem skeleton with Bundler it provides the following defaults:
$ bundle gem foo; tree foo
foo
├── Gemfile
├── README.md
├── Rakefile
├── bin
│   ├── console
│   └── setup
├── foo.gemspec
├── lib
│   ├── foo
│   │   └── version.rb
│   └── foo.rb
└── spec
├── foo_spec.rb
└── spec_helper.rb
4 directories, 10 files
The default Rakefile is set up to run RSpec, but you can certainly implement the Rakefile's default task and your tests any way you'd like.

Testing Jekyll plugin with Cucumber

I am developing a plugin for Jekyll and want to use Cucumber and Aruba to test it. My plugin is currently just adding a command to Jekyll like it specifies here: https://jekyllrb.com/docs/plugins/#commands I'm bundling this as a gem.
This is the structure of my gem folder:
├── Gemfile
├── Gemfile.lock
├── LICENSE.txt
├── README.md
├── Rakefile
├── bin
│   ├── console
│   └── setup
├── features
│   ├── hello_world.feature
│   ├── step_definitions.rb
│   └── support
│   └── env.rb
├── hello_world.gemspec
├── lib
│   ├── hello_world
│   │   └── version.rb
│   └── hello_world.rb
├── pkg
│   └── hello_world-0.1.0.gem
└── spec
├── hello_world_spec.rb
└── spec_helper.rb
This is my lib/hello_world.rb file:
require "hello_world/version"
require "jekyll"
module Jekyll
module Commands
class Hello < Command
class << self
def init_with_program(prog)
prog.command(:hello) do |c|
c.action do |args, options|
Jekyll.logger.info "Hello world!"
end
end
end
end
end
end
end
Here is my features/hello_world.feature file:
Feature: Hello world
As a hacker who likes to blog
I want to have a custom Jekyll command
Scenario: Show Hello world
When I run `jekyll hello`
Then the output should contain "Hello world!"
And the exit status should be 0
However this is what I get:
$ cucumber
Feature: Hello world
As a hacker who likes to blog
I want to have a custom Jekyll command
Scenario: Show Hello world # features/hello_world.feature:6
When I run `jekyll hello` # aruba-0.14.2/lib/aruba/cucumber/command.rb:13
Then the output should contain "Hello world!" # aruba-0.14.2/lib/aruba/cucumber/command.rb:159
expected "fatal: 'jekyll hello' could not be found. You may need to install the jekyll-hello gem or a related gem to be able to use this subcommand. " to string includes: "hello world" (RSpec::Expectations::ExpectationNotMetError)
features/hello_world.feature:8:in `Then the output should contain "Hello world!"'
And the exit status should be 0 # aruba-0.14.2/lib/aruba/cucumber/command.rb:277
Failing Scenarios:
cucumber features/hello_world.feature:6 # Scenario: Help show a helpful message
1 scenario (1 failed)
3 steps (1 failed, 1 skipped, 1 passed)
I believe that the issue must have something to do with the way Jekyll plugins are required: https://jekyllrb.com/docs/plugins/#installing-a-plugin They are supposed to be required in the :jekyll_plugins group of the Gemfile.
In my Gemfile I have tried just referencing just the gemspec and trying to include the gemspec into the :jekyll_plugins group like this and neither option has worked:
source 'https://rubygems.org'
gemspec :group => :jekyll_plugins
I have also tried building my gem, installing it locally and then using it in a test Jekyll build (including via the :jekyll_plugins group in the Gemfile) and it works correctly.
How can I get my plugin's command to "register" correctly with Jekyll so that it is available for my Cucumber test?
One way to achieve this would by defining a step-definition that creates a fixture site with the proper Gemfile
Feature: Hello world
As a hacker who likes to blog
I want to have a custom Jekyll command
Scenario: Show Hello world
Given I have a fixture site
When I run `jekyll hello`
Then the output should contain "Hello world!"
And the exit status should be 0
In the above, executing the Given step is supposed to create a fixture site with a Gemfile that points to your plugin and a basic jekyll-config file. (Edit your step_definitions.rb file to do so)
# Gemfile
gem "jekyll", "~> 3.6"
group :jekyll_plugins do
# point to the location of your plugin's gemspec, relative to this file
gem "hello_world", :path => "path/to/repo root"
end

How to make C library for ffi during `gem install`

Recently I wrote a gem using ffi. With this help, the ruby code can call the C extensions with a dynamic library, which I compile all C extensions into.
My gem structure is like this:
.
├── ext
│   ├── common.c
│   ├── common.h
│   ├── Makefile
│   └── ...
├── Gemfile
├── lib
│   ├── ...
│   │   └── version.rb
│   └── ...
├── Rakefile
├── README.md
└── test
What I plan to do is running make in ./ext, then everything goes ok.
But, the problem occurs. If other users install my gem with gem install, how to make C library for themselves?
Is any one willing to offer me a help? Any idea is welcome.
Take a look at ffi-compiler. There's an example on the gem's page:
Example
Directory layout
lib
|- example
|- example.rb
ext
|- example.c
|- Rakefile
example.gemspec
lib/example/example.rb
require 'ffi'
require 'ffi-compiler/loader'
module Example
extend FFI::Library
ffi_lib FFI::Compiler::Loader.find('example')
# example function which takes no parameters and returns long
attach_function :example, [], :long
end
ext/example.c
long
example(void)
{
return 0xdeadbeef;
}
ext/Rakefile
require 'ffi-compiler/compile_task'
FFI::Compiler::CompileTask.new('example') do |c|
c.have_header?('stdio.h', '/usr/local/include')
c.have_func?('puts')
c.have_library?('z')
end
example.gemspec
Gem::Specification.new do |s|
s.extensions << 'ext/Rakefile'
s.name = 'example'
s.version = '0.0.1'
s.email = 'ffi-example'
s.files = %w(example.gemspec) + Dir.glob("{lib,spec,ext}/**/*")
s.add_dependency 'rake'
s.add_dependency 'ffi-compiler'
end
Build gem and install it
gem build example.gemspec && gem install example-0.0.1.gem
Successfully built RubyGem
Name: example
Version: 0.0.1
File: example-0.0.1.gem
Building native extensions. This could take a while...
Successfully installed example-0.0.1
Test it
$ irb
2.0.0dev :001 > require 'example/example'
=> true
2.0.0dev :002 > puts "Example.example=#{Example.example.to_s(16)}"
Example.example=deadbeef
=> nil
You can run make command during gem installation this way:
add spec.extensions << 'ext/mkrf_conf.rb' to your gemspec.
create ext/mkrf_conf.rb file:
task :default { `make` }
enjoy!

Creating Ruby Gem

I'm learning ruby and I wanted to test how to create gem file. I have followings installed in my machine.
ruby 1.9.3p362 (2012-12-25 revision 38607) [x86_64-linux]
Bundler version 1.2.3
rake, version 10.0.3
I created a gem using bundle gem hello_gem. I added following sample code to hello_gem.rb
module HelloGem
class Base
def self.hello
puts "Hello Ruby Gem #{HelloGem::VERSION}"
end
end
end
My folder structure is like follows.
├── Gemfile
├── Gemfile.lock
├── hello_gem.gemspec
├── lib
│   ├── hello_gem
│   │   └── version.rb
│   └── hello_gem.rb
├── LICENSE.txt
├── Rakefile
├── README.md
Then I created the gem using rake install. Then I started irb and I can execute following.
1.9.3-p362 :001 > require 'hello_gem'
=> true
1.9.3-p362 :002 > HelloGem::Base.hello
Hello Ruby Gem 0.0.1
=> nil
1.9.3-p362 :003 >
Problem comes when I wanted to move code to lib folder. I created lib/hello_gem/base.rb and added the above code there. And in the hello_gem.rb I just used require "hello_gem/base". Now my project look like follows.
├── Gemfile
├── Gemfile.lock
├── hello_gem.gemspec
├── lib
│   ├── hello_gem
│   │   ├── base.rb
│   │   └── version.rb
│   └── hello_gem.rb
├── LICENSE.txt
├── Rakefile
├── README.md
When I build the gem using rake install and use irb to test following error happened.
1.9.3-p362 :001 > require 'hello_gem'
LoadError: cannot load such file -- hello_gem/base
from /home/sandarenu/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/site_ruby/1.9.1/rubygems/custom_require.rb:36:in `require'
from /home/sandarenu/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/site_ruby/1.9.1/rubygems/custom_require.rb:36:in `require'
from /home/sandarenu/.rvm/gems/ruby-1.9.3-p362/gems/hello_gem-0.0.1/lib/hello_gem.rb:2:in `<top (required)>'
from /home/sandarenu/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/site_ruby/1.9.1/rubygems/custom_require.rb:60:in `require'
from /home/sandarenu/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/site_ruby/1.9.1/rubygems/custom_require.rb:60:in `rescue in require'
from /home/sandarenu/.rvm/rubies/ruby-1.9.3-p362/lib/ruby/site_ruby/1.9.1/rubygems/custom_require.rb:35:in `require'
from (irb):1
from /home/sandarenu/.rvm/rubies/ruby-1.9.3-p362/bin/irb:16:in `<main>'
1.9.3-p362 :002 >
I can't find a way to fix this issue. It would be a great help if somebody can tell me what I'm doing wrong here.
Thanks in advance.
The .gemspec created by bundle gem uses Git to determine which files to include in the gem; it contains the line:
gem.files = `git ls-files`.split($/)
In order for it to add your hello_gem/base.rb you need to add it to the Git repository. Since the original setup works for you I assume you have Git installed, so you just need to run:
git add lib/hello_gem/base.rb
You don’t actually need to commit the file for git ls-files to pick it up and add it to the gem, so this should be enough to get it to work.

required file not seen on heroku in jekyll blog

This is my dir structure
.
├── _layouts
│   └── default.html
├── _posts
├── _site
│   ├── config.ru
│   ├── devart.rb
│   └── index.html
├── config.ru
├── devart.rb
└── index.html
My config.ru
require 'devart.rb'
run Sinatra::Application
When I push this to heroku in the log files I see this error saying devart file is not found during the require. What the heck am I doing wrong??
<internal:lib/rubygems/custom_require>:29:in `require': no such file to load -- devart (LoadError)
Ruby 1.9.2 no longer has current dir in loadpath. So changed to
require './devart.rb'
to get it to work.

Resources