Giter Club home page Giter Club logo

gem-compiler's Introduction

gem-compiler

A RubyGems plugin that generates binary (pre-compiled) gems.

Gem Version Maintainability

Description

gem-compiler is a RubyGems plugin that helps generates binary gems from already existing ones without altering the original source code. It compiles Ruby C extensions and bundles the result into a new gem.

It uses an outside-in approach and leverages on existing RubyGems code to do it.

Benefits

Using gem-compiler removes the need to install a compiler toolchain on the platform used to run the extension. This means less dependencies are required in those systems and can reduce associated update/maintenance cycles.

Additionally, by having only binaries, it reduces the time it takes to install several gems that normally take minutes to compile themselves and the needed dependencies.

Without gem-compiler, takes more than a minute to install Nokogiri on Ubuntu 18.04:

$ time gem install --local nokogiri-1.10.7.gem
Building native extensions. This could take a while...
Successfully installed nokogiri-1.10.7
1 gem installed

real    1m22.670s
user    1m5.856s
sys     0m18.637s

Compared to the installation of the pre-compiled version:

$ gem compile nokogiri-1.10.7.gem --prune
Unpacking gem: 'nokogiri-1.10.7' in temporary directory...
Building native extensions. This could take a while...
  Successfully built RubyGem
  Name: nokogiri
  Version: 1.10.7
  File: nokogiri-1.10.7-x86_64-linux.gem

$ time gem install --local nokogiri-1.10.7-x86_64-linux.gem
Successfully installed nokogiri-1.10.7-x86_64-linux
1 gem installed

real    0m1.697s
user    0m1.281s
sys     0m0.509s

Installation

To install gem-compiler you need to use RubyGems:

$ gem install gem-compiler

Which will fetch and install the plugin. After that the compile command will be available through gem.

Usage

As requirement, gem-compiler can only compile local gems, either one you have generated from your projects or previously downloaded.

Fetching a gem

If you don't have the gem locally, you can use fetch to retrieve it first:

$ gem fetch yajl-ruby --platform=ruby
Fetching: yajl-ruby-1.1.0.gem (100%)
Downloaded yajl-ruby-1.1.0

Please note that I was explicit about which platform to fetch. This will avoid RubyGems attempt to download any existing binary gem for my current platform.

Compiling a gem

You need to tell RubyGems the filename of the gem you want to compile:

$ gem compile yajl-ruby-1.1.0.gem

The above command will unpack, compile any existing extensions found and repackage everything as a binary gem:

Unpacking gem: 'yajl-ruby-1.1.0' in temporary directory...
Building native extensions.  This could take a while...
  Successfully built RubyGem
  Name: yajl-ruby
  Version: 1.1.0
  File: yajl-ruby-1.1.0-x86-mingw32.gem

This new gem do not require a compiler, as shown when locally installed:

C:\> gem install --local yajl-ruby-1.1.0-x86-mingw32.gem
Successfully installed yajl-ruby-1.1.0-x86-mingw32
1 gem installed

There are native gems that will invalidate their own specification after compile process completes. This will not permit them be repackaged as binary gems. To workaround this problem you have the option to prune the package process:

$ gem fetch nokogiri --platform=ruby
Fetching: nokogiri-1.6.6.2.gem (100%)
Downloaded nokogiri-1.6.6.2

$ gem compile nokogiri-1.6.6.2.gem --prune
Unpacking gem: 'nokogiri-1.6.6.2' in temporary directory...
Building native extensions.  This could take a while...
  Successfully built RubyGem
  Name: nokogiri
  Version: 1.6.6.2
  File: nokogiri-1.6.6.2-x86_64-darwin-12.gem

$ gem install --local nokogiri-1.6.6.2-x86_64-darwin-12.gem
Successfully installed nokogiri-1.6.6.2-x86_64-darwin-12
1 gem installed

Restricting generated binary gems

Gems compiled with gem-compiler be lock to the version of Ruby used to compile them, following Ruby's ABI compatibility (MAJOR.MINOR)

This means that a gem compiled with Ruby 2.6.1 could be installed in any version of Ruby 2.6.x (Eg. 2.6.4).

You can tweak this behavior by using --abi-lock option during compilation. There are 3 available modes:

  • ruby: Follows Ruby's ABI. Gems compiled with Ruby 2.6.1 can be installed in any Ruby 2.6.x (default behavior).
  • strict: Uses Ruby's full version. Gems compiled with Ruby 2.6.1 can only be installed in Ruby 2.6.1.
  • none: Disables Ruby compatibility. Gems compiled with this option can be installed on any version of Ruby (alias for --no-abi-lock).

Warning: usage of none is not recommended since different versions of Ruby might expose different APIs. The binary might be expecting specific features not present in the version of Ruby you're installing the gem into.

Reducing extension's size (stripping)

By default, RubyGems do not strip symbols from compiled extensions, including debugging information and can result in increased size of final package.

With --strip, you can reduce extensions by using same stripping options used by Ruby itself (see RbConfig::CONFIG["STRIP"]):

$ gem compile oj-3.10.0.gem --strip
Unpacking gem: 'oj-3.10.0' in temporary directory...
Building native extensions. This could take a while...
Stripping symbols from extensions (using 'strip -S -x')...
  Successfully built RubyGem
  Name: oj
  Version: 3.10.0
  File: oj-3.10.0-x86_64-linux.gem

Or you can provide your own stripping command instead:

$ gem compile oj-3.10.0.gem --strip "strip --strip-unneeded"
Unpacking gem: 'oj-3.10.0' in temporary directory...
Building native extensions. This could take a while...
Stripping symbols from extensions (using 'strip --strip-unneeded')...
  Successfully built RubyGem
  Name: oj
  Version: 3.10.0
  File: oj-3.10.0-x86_64-linux.gem

Append build number to gem version

Gem servers like RubyGems or Gemstash treat gems as immutable, so once a gem has been pushed, you cannot replace it.

When playing with compilation options or library dependencies, you might require to build and push an updated version of the same version.

You can use --build-number to add the build number to the compiled version and push an updated build, maintaining gem dependency compatibility:

$ gem compile oj-3.11.3.gem --build-number 10
Unpacking gem: 'oj-3.11.3' in temporary directory...
Building native extensions. This could take a while...
  Successfully built RubyGem
  Name: oj
  Version: 3.11.3.10
  File: oj-3.11.3.10-x86_64-linux.gem

This new version remains compatible with RubyGems' dependency requirements like ~> 3.11 or ~> 3.11.3.

Compiling from Rake

Most of the times, as gem developer, you would like to generate both kind of gems at once. For that purpose, you can add a task for Rake similar to the one below:

desc "Generate a pre-compiled native gem"
task "gem:native" => ["gem"] do
  sh "gem compile #{gem_file}"
end

Of course, that assumes you have a task gem that generates the base gem required.

Requirements

Ruby and RubyGems

It's assumed you have Ruby and RubyGems installed. gem-compiler requires RubyGems 2.6.x to work.

If you don't have RubyGems 2.6.x, you can upgrade by running:

$ gem update --system

A compiler

In order to compile a gem, you need a compiler toolchain installed. Depending on your Operating System you will have one already installed or will require additional steps to do it. Check your OS documentation about getting the right one.

If you're using Windows

For those using RubyInstaller-based builds, you will need to download the DevKit from their downloads page and follow the installation instructions.

To be sure your installation of Ruby is based on RubyInstaller, execute at the command prompt:

C:\> ruby --version

And from the output:

ruby 2.4.9p362 (2019-10-02 revision 67824) [x64-mingw32]

If you see mingw32, that means you're using a RubyInstaller build (MinGW based).

Differences with rake-compiler

rake-compiler has provided to Ruby library authors a tool for compiling extensions and generating binary gems of their libraries.

You can consider rake-compiler's approach be an inside-out process. To do its magic, it requires library authors to modify their source code, adjust some structure and learn a series of commands.

While the ideal scenario is using a tool like rake-compiler that endorses convention over configuration, is not humanly possible change all the projects by snapping your fingers ๐Ÿ˜‰

License

The MIT License

gem-compiler's People

Contributors

andrasio avatar luislavena avatar mbland avatar mgoggin avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gem-compiler's Issues

Uploading multiple versions of ABI to rubygems

Hi Luis,

My question probably not related to gem-compiler in particular. What if I've built two versions of binary gem for the same platform (say x86_64-linux). One with ABI 2.7 and another with 2.6. When I upload one of them to rubygems, it does not allow me to upload another complaining about republishing.

But when I later try to install without --platform=ruby it by default download binary version regardless the ABI I'm using right now.

So the actual question is, how to resolve this problem? Is it even possible to have separate packages on rubygems that compiled against different ABI? I can see it in gem's metadata, then why it does not use this information during install and does not allow to upload multiple versions of binaries.

modify the version of the gem

Is it possible to alter the version of the fat gem ?

I would like to use a version like that: 1.0.0.20200525 for the fat gem when the gem is itself 1.0.0.

Why ?

Because if there is a problem with the fat gem after being uploaded in a local gemstash, there is no way to push a new one with the same version and we are dependant of the creation of a new version in the upstream gem.

Support for latest Rubygems 2.2.x?

Are there any plans for supporting ruby gems 2.2? It doesn't seem to work against that version because of some of the ways temp directories are needed.

Thanks!!!

No issues, just wanted to say thanks for this incredibly useful piece of work. When I was struggling to install prawn on a non-techy colleague's computer because it now relies on a compiled racc, I discovered gem-compiler and it worked for me first time, exactly as set out in the readme. I am very grateful.

Now I've created extra work for you in having to file this non-issue, so sorry about that, but you deserve to know how helpful the software has been.

gem-compile fails when gem has no Gemfile (SUSE/OpenSUSE)

Hi Luis,

A few years on, I have found a blocking issue. I have found that some gems (with native extensions) do not have a Gemfile, and that this causes gem compilation to fail.

Config:

gem 2.7.6.2
gem-compiler 0.9.0 
ruby 2.5.8p224 (2020-03-31 revision 67882) [x86_64-linux-gnu]
OS SLES15 SP2 docker container.

Examples of failing gems:

libxml-ruby-3.2.1.gem
mysql2-0.5.3.gem
passenger-6.0.8.gem
racc-1.5.2.gem
ruby_deep_clone-0.8.0.gem
websocket-driver-0.7.3.gem

With some hacking I found that Gem::Package.build(gemspec) is what appears to expect the Gemfile, and that creating an empty one with a touch command is sufficient to fix the issue. Strictly speaking I guess this should be fixed in Gem::Package, but as it is gem-compiler that suffers - I'm not sure they'd have the insentive to fix this.

Adding the unindented line in /usr/lib64/ruby/gems/2.5.0/gems/gem-compiler-0.9.0/lib/rubygems/compiler.rb seems to do the trick, though it's probably not the most ruby-like of solutions ...

  def repackage(gemspec)
    # clear out extensions from gemspec
    gemspec.extensions.clear

    # adjust platform
    gemspec.platform = Gem::Platform::CURRENT

    # adjust version of Ruby
    adjust_abi_lock(gemspec)

    # build new gem
    output_gem = nil

    Dir.chdir target_dir do
system("touch #{@target_dir}/Gemfile")
      output_gem = Gem::Package.build(gemspec)
    end

    unless output_gem
      raise CompilerError,
            "There was a problem building the gem."
    end

    # move the built gem to the original output directory
    FileUtils.mv File.join(target_dir, output_gem), @output_dir

    # return the path of the gem
    output_gem
  end

Would you consider a fix?

cheers,
Stefan

For completeness, adding stack-trace:

# gem compile racc-1.5.2.gem 
Unpacking gem: 'racc-1.5.2' in temporary directory...
Building native extensions. This could take a while...
ERROR:  While executing gem ... (Errno::ENOENT)
    No such file or directory @ rb_file_s_stat - Gemfile
	/usr/lib64/ruby/2.5.0/rubygems/package.rb:117:in `stat'
	/usr/lib64/ruby/2.5.0/rubygems/package.rb:117:in `build'
	/usr/lib64/ruby/gems/2.5.0/gems/gem-compiler-0.9.0/lib/rubygems/compiler.rb:182:in `block in repackage'
	/usr/lib64/ruby/gems/2.5.0/gems/gem-compiler-0.9.0/lib/rubygems/compiler.rb:180:in `chdir'
	/usr/lib64/ruby/gems/2.5.0/gems/gem-compiler-0.9.0/lib/rubygems/compiler.rb:180:in `repackage'
	/usr/lib64/ruby/gems/2.5.0/gems/gem-compiler-0.9.0/lib/rubygems/compiler.rb:45:in `compile'
	/usr/lib64/ruby/gems/2.5.0/gems/gem-compiler-0.9.0/lib/rubygems/commands/compile_command.rb:78:in `execute'
	/usr/lib64/ruby/2.5.0/rubygems/command.rb:313:in `invoke_with_build_args'
	/usr/lib64/ruby/2.5.0/rubygems/command_manager.rb:173:in `process_args'
	/usr/lib64/ruby/2.5.0/rubygems/command_manager.rb:143:in `run'
	/usr/lib64/ruby/2.5.0/rubygems/gem_runner.rb:59:in `run'
	/usr/bin/gem:21:in `<main>'

Compiler errors when it finds nothing to do?

I tried to set up a loop compiling all the dependencies of the thing I was working on.

  • gem compile addressable-2.5.2.gem
    ERROR: While executing gem ... (Gem::Compiler::CompilerError)
    There are no extensions to build on this gem file.
    Unpacking gem: 'addressable-2.5.2' in temporary directory...

Benchmarks?

Do you have any benchmark to show how does this improves build time?

cannot compile nokogiri

I tried on CentOS 7(ruby 2.0.0p598) and Mac(ruby 2.1.5p273, ruby 2.0.0p481). both failed

# gem fetch nokogiri
Fetching: nokogiri-1.6.6.2.gem (100%)
Downloaded nokogiri-1.6.6.2
# gem compile nokogiri-1.6.6.2.gem -- --use-system-libraries
Unpacking gem: 'nokogiri-1.6.6.2' in temporary directory...
Building native extensions with: '--use-system-libraries'
This could take a while...
WARNING:  See http://guides.rubygems.org/specification-reference/ for help
ERROR:  While executing gem ... (Gem::InvalidSpecificationException)
    ["ports/archives/libxml2-2.9.2.tar.gz", "ports/archives/libxslt-1.1.28.tar.gz"] are not files
# gem compile nokogiri-1.6.6.2.gem
Unpacking gem: 'nokogiri-1.6.6.2' in temporary directory...
Building native extensions.  This could take a while...
WARNING:  See http://guides.rubygems.org/specification-reference/ for help
ERROR:  While executing gem ... (Gem::InvalidSpecificationException)
    ["ports/archives/libxml2-2.9.2.tar.gz", "ports/archives/libxslt-1.1.28.tar.gz", "ports/patches/libxml2/0001-Revert-Missing-initialization-for-the-catalog-module.patch", "ports/patches/libxml2/0002-Fix-missing-entities-after-CVE-2014-3660-fix.patch", "ports/patches/libxslt/0001-Adding-doc-update-related-to-1.1.28.patch", "ports/patches/libxslt/0002-Fix-a-couple-of-places-where-f-printf-parameters-wer.patch", "ports/patches/libxslt/0003-Initialize-pseudo-random-number-generator-with-curre.patch", "ports/patches/libxslt/0004-EXSLT-function-str-replace-is-broken-as-is.patch", "ports/patches/libxslt/0006-Fix-str-padding-to-work-with-UTF-8-strings.patch", "ports/patches/libxslt/0007-Separate-function-for-predicate-matching-in-patterns.patch", "ports/patches/libxslt/0008-Fix-direct-pattern-matching.patch", "ports/patches/libxslt/0009-Fix-certain-patterns-with-predicates.patch", "ports/patches/libxslt/0010-Fix-handling-of-UTF-8-strings-in-EXSLT-crypto-module.patch", "ports/patches/libxslt/0013-Memory-leak-in-xsltCompileIdKeyPattern-error-path.patch", "ports/patches/libxslt/0014-Fix-for-bug-436589.patch", "ports/patches/libxslt/0015-Fix-mkdir-for-mingw.patch", "ports/patches/sort-patches-by-date"] are not files

Compile targeting a different platform of Ruby (cross-compilation)

In order to produce binaries for another platform, I should be able to indicate the platform so cross-compilation could happen.

Add cross-compiled Ruby to the list of interpreters

Using interpreters command, user should be able to add Ruby interpreters for platforms other than the current one.

Compile to a different platform

$ gem compile GEMFILE --platform=x86-mingw32

Specify the platform (which is mentioned in the list of interpreters) to target the compilation.

If no --ruby or --abi is provided, it will assume the current Ruby version but targeting the specified platform.

If such version is not found, it should trigger an error.

Compile to different platform and version

$ gem compile GEMFILE --ruby=1.8.7 --platform=x86-mingw32

Combining both --platfrom and --ruby (or --abi) provides the maximum flexibility when current Ruby version is not the same you are targeting.

When used, the Ruby version, patchlevel, platform and ABI will be displayed:

Unpacking gem: 'yajl-ruby-1.1.0' in temporary directory...
Using Ruby 1.8.7-p358 (1.8) (x86-mingw32, non-native)...
Building native extensions.  This could take a while...

An additional non-native legend is displayed, indicating that using is actually attempting to mimic the target configuration.

Notes about cross-compilation

Under some circumstances, certain versions of Ruby cannot be cross-compiled when the current Ruby is greater than the target.

For example, it fails to cross-compile to Ruby 1.8.7 when current Ruby version is 1.9.3. However, cross-compile to 1.9.3 from 1.8.7 is possible.

This is a Ruby limitation that you need to keep in mind when using this functionality.

Compile using a different version of Ruby

In order to produce binary gems for other versions of Ruby, I should be able to indicate the version of Ruby or compatibility version (ABI) desired.

Using a specific Ruby version

$ gem compile GEMFILE --ruby=1.9.3[-p194]

You can specify the version (X.Y.Z) of a Ruby interpreter that is already identified (using interpreters command)

If patchlevel (-pN) is omitted, the latest patchlevel for the specified version will be used.

This option will always use the same platform as the Ruby used to invoke the compilation process.

When used, Ruby version, patchlevel, platform and ABI will be displayed during compilation:

Unpacking gem: 'yajl-ruby-1.1.0' in temporary directory...
Using Ruby 1.9.3-p194 (1.9.1) (x86-mingw32)...
Building native extensions.  This could take a while...
  Successfully built RubyGem
...

A version is required for --ruby option to work.

Using a compatiblity version (ABI)

$ gem compile GEMFILE --abi=1.9.1

Instead of using a specific version, gem-compiler can accept the ABI (Application Binary Interface) and select the latest Ruby version that matches the requested one.

At this time two known ABIs exists: 1.9.1 (for Ruby 1.9.1 to 1.9.3) and 1.8(for Ruby 1.8.5 to 1.8.7).

When used, the information of the used Ruby version will be displayed during compilation:

Unpacking gem: 'yajl-ruby-1.1.0' in temporary directory...
Using Ruby 1.8.7-p358 (1.8) (x86-mingw32)...
Building native extensions.  This could take a while...
...

A version is required for --abi option to work.

Considerations about compiling to different versions

To be able to compile to a different version of Ruby, gem-compiler need to invoke the different Ruby interpreter while inside your current one.

Under some circumstances this process could fail, mostly caused by tools that manage or allow user switching of interpreters (RVM and rbenv).

Please be aware of this before reporting any issue.

Lock down Ruby's ABI when packaging new gem

The compiled extensions are bind to the specific version of Ruby used.

At this time, required_ruby_version in the gem specification is not updated, which should be to avoid installation of incorrect binaries on different versions.

Perhaps we should use RbConfig::CONFIG["ruby_version"] as ABI indicator:

$ ruby -rrbconfig -ve 'p RbConfig::CONFIG["ruby_version"]'
ruby 1.8.7 (2013-06-27 patchlevel 374) [i686-darwin14.0.0]
"1.8"

$ ruby -rrbconfig -ve 'p RbConfig::CONFIG["ruby_version"]'
ruby 1.9.3p551 (2014-11-13) [x86_64-darwin14.0.0]
"1.9.1"

$ ruby -rrbconfig -ve 'p RbConfig::CONFIG["ruby_version"]'
ruby 2.0.0p598 (2014-11-13) [x86_64-darwin14.0.0]
"2.0.0"

$ ruby -rrbconfig -ve 'p RbConfig::CONFIG["ruby_version"]'
ruby 2.1.5p273 (2014-11-13 revision 48405) [x86_64-darwin14.0]
"2.1.0"

Use only in gemfile?

Somewhat similar to #8 but here is my case:

I have a unique process that will collect all needed gems and distribute them as a single application to windows. I do this on OS X.

So I have successfully created unf_ext-0.0.7.4-x86-mingw32.gem on windows, moved that to my project, and added this to the Gemfile

gem 'unf_ext', :path => './'

but when I try to work my rake tasks on OS X, I get

rake package                                                                                                                                               โŽ
Could not find gem 'unf_ext' in source at `./`.
Source does not contain any versions of 'unf_ext'
Run `bundle install` to install missing gems.

Which I guess is because the binary formats are different, but there must be a way to get around this because it shouldn't matter, we're just moving it around, not running it.

Specific case: puma with SSL support

As explained here: puma/puma#2277

I try to make a native gem of puma with SSL support and I don't succeed.

gem-compiler does create a puma-4.3.3-x86_64-linux.gem but it doesn't seem to have SSL support as the error log below can show:

gemstash_1  | /home/ruby/vendor/bundle/ruby/2.5.0/gems/puma-4.3.3-x86_64-linux/lib/puma/minissl/context_builder.rb:6:in `check': SSL not available in this build (StandardError)
gemstash_1  | 	from /home/ruby/vendor/bundle/ruby/2.5.0/gems/puma-4.3.3-x86_64-linux/lib/puma/minissl/context_builder.rb:6:in `initialize'
gemstash_1  | 	from /home/ruby/vendor/bundle/ruby/2.5.0/gems/puma-4.3.3-x86_64-linux/lib/puma/binder.rb:158:in `new'
gemstash_1  | 	from /home/ruby/vendor/bundle/ruby/2.5.0/gems/puma-4.3.3-x86_64-linux/lib/puma/binder.rb:158:in `block in parse'
gemstash_1  | 	from /home/ruby/vendor/bundle/ruby/2.5.0/gems/puma-4.3.3-x86_64-linux/lib/puma/binder.rb:90:in `each'
gemstash_1  | 	from /home/ruby/vendor/bundle/ruby/2.5.0/gems/puma-4.3.3-x86_64-linux/lib/puma/binder.rb:90:in `parse'
gemstash_1  | 	from /home/ruby/vendor/bundle/ruby/2.5.0/gems/puma-4.3.3-x86_64-linux/lib/puma/cluster.rb:436:in `run'
gemstash_1  | 	from /home/ruby/vendor/bundle/ruby/2.5.0/gems/puma-4.3.3-x86_64-linux/lib/puma/launcher.rb:172:in `run'
gemstash_1  | 	from /home/ruby/vendor/bundle/ruby/2.5.0/gems/puma-4.3.3-x86_64-linux/lib/puma/cli.rb:80:in `run'
gemstash_1  | 	from /home/ruby/vendor/bundle/ruby/2.5.0/gems/gemstash-2.1.0/lib/gemstash/cli/start.rb:16:in `run'
gemstash_1  | 	from /home/ruby/vendor/bundle/ruby/2.5.0/gems/gemstash-2.1.0/lib/gemstash/cli.rb:79:in `start'
gemstash_1  | 	from /home/ruby/vendor/bundle/ruby/2.5.0/gems/thor-0.20.3/lib/thor/command.rb:27:in `run'
gemstash_1  | 	from /home/ruby/vendor/bundle/ruby/2.5.0/gems/thor-0.20.3/lib/thor/invocation.rb:126:in `invoke_command'
gemstash_1  | 	from /home/ruby/vendor/bundle/ruby/2.5.0/gems/thor-0.20.3/lib/thor.rb:387:in `dispatch'
gemstash_1  | 	from /home/ruby/vendor/bundle/ruby/2.5.0/gems/thor-0.20.3/lib/thor/base.rb:466:in `start'
gemstash_1  | 	from /home/ruby/vendor/bundle/ruby/2.5.0/gems/gemstash-2.1.0/lib/gemstash/cli.rb:34:in `start'
gemstash_1  | 	from /home/ruby/vendor/bundle/ruby/2.5.0/gems/gemstash-2.1.0/exe/gemstash:6:in `<top (required)>'
gemstash_1  | 	from /home/ruby/vendor/bundle/ruby/2.5.0/bin/gemstash:23:in `load'
gemstash_1  | 	from /home/ruby/vendor/bundle/ruby/2.5.0/bin/gemstash:23:in `<main>'

Using these compiled gems with bundle

I compiled a gem successfully but now I want to use it with bundle.

In directory A, I have vendor/cache and into vendor cache I put unf_ext-0.0.7.4-x86-mingw32.gem, the compiled gem, but when I try to use it with bundle like so: bundle install --local --path ../vendor --without development, I get this error:

Some gems seem to be missing from your vendor/cache directory.
Could not find unf_ext-0.0.7.4 in any of the sources

So what is the correct way to use these precompiled gems with bundle?

Fail when artifacts are empty?

Hi! I just had an issue with http-parser. It built correctly but the library was missing because the rake task wasn't copying it into lib/, the gem requires it directly from ext/.

So I had to add --include-shared-dir ext/ to the build, but I think it could be better if gem compiler fails if there aren't artifacts to add to the binary gem.

I can send a PR :)

Collect information about installed Rubies

In order to compile a gem using a different version of Ruby, I should be able to collect a list of Ruby interpreters that will be used at compilation to select a specific version.

A proposed CLI for this will be an additional RubyGems command named interpreters.

This information will be stored in ~/.gem/interpreters so it can be shared across multiple interpreters.

Add a interpreter

$ gem interpreters --add [/path/to/interpreter/ruby]

Where /path/to/interpreter/ruby is the location of ruby executable that will be analyzed and later on added to the list.

If an interpreter is found at indicated location, a message similar to the one below should be displayed:

Ruby interpreter found.
       path: C:/Ruby193/bin/ruby.exe
    version: 1.9.3
 patchlevel: 194
   platform: x86-mingw32
        abi: 1.9.1

When no path is provided, it will analyze and store current Ruby.

Adding the same interpreter path twice should not result in multiple entries but instead a refresh of existing information.

List interpreters

$ gem interpreters [--list]

Obtain a list of interpreters grouped by platform. Expected output:

*** Ruby interpreters ***

x86-mingw32
  1.9.3-p194
    path: C:/Ruby193/bin/ruby.exe
     abi: 1.9.1

  1.8.7-p358
    path: C:/Ruby187/bin/ruby.exe
     abi: 1.8

x64-mingw32
  2.0.0dev
    path: V:/ruby-dev/bin/ruby.exe
     abi: 2.0.0

The list of interpreters is the default behavior if no suboptions is provided.

Remove interpreter

$ gem interpreters --remove [/path/to/interpreter/ruby]

This removes any recorded interpreter from the list.

If /path/to/interpreter/ruby didn't exist in the list, a note will be displayed to the user:

No record of '/path/to/interpreter/ruby' exists in interpreters list.

When no path is provided, it will remove from the list current Ruby (same behavior than --add)

Package not found

Hi,

I've tried to make some binaries for my Synology lately and i got atomic and json to compile.
Installation seems to work also:

/usr/local/ruby/bin/gem install json-1.8.1-x86-linux.gem 
Successfully installed json-1.8.1-x86-linux
Parsing documentation for json-1.8.1-x86-linux
unable to convert "\x80" from ASCII-8BIT to UTF-8 for lib/json/ext/generator.so, skipping
unable to convert "\xB4" from ASCII-8BIT to UTF-8 for lib/json/ext/parser.so, skipping
1 gem installed

but when i try to start the app with rake i still get the following output:

/usr/local/ruby/bin/rake db:create
Could not find json-1.8.1 in any of the sources
Run `bundle install` to install missing gems.

gem list --local lists them correctly:

atomic (1.1.16 x86-linux, 1.1.14 x86-linux)
json (1.8.1 x86-linux, 1.7.7)

also i dont know why there is a x86-linux suffix and if maybe thats the problem?

running bundle install isn't really an option here because i don't have any compiler on the target system so that will fail too.

Gem::Installer::ExtensionBuildError: ERROR: Failed to build gem native extension.
An error occurred while installing json (1.8.1), and Bundler cannot continue.
Make sure that `gem install json -v '1.8.1'` succeeds before bundling.

load relative libruby.so

Path to libruby.so is hardcoded in the compiled gem:

$ ldd /home/mpapis/.rvm/gems/ruby-1.9.3-p125/gems/nokogiri-1.5.2-x86_64-linux/lib/nokogiri/nokogiri.so
...
libruby.so.1.9 => /home/mpapis/.rvm/rubies/ruby-1.9.3-p194/lib64/libruby.so.1.9 (0x00007f7c5968a000)
...

gem was compiled in 1.9.3-p194 and installed in 1.9.3-p125.

ruby has a switch --enable-load-relative which in my understanding should be equivalent to (Linux):

LDFLAGS="-Wl,-rpath,../lib,-rpath,../lib64,-rpath,../libexec"

I have added the lib64 / libexec as at least lib64 might be happening on some systems (like mine), I'm not sure if the ../ is proper way to make it working.

Not sure what effects --enable-load-relative has on OSX and if there is anything needed for Windows.

Using gem compiler in a restricted server environment

Hi Luis,

I am aiming to have a production machine running a Ruby on Rails app. However, this production machine is managed with Plesk and allows only very rudimentary access, but was promised to be able to run Ruby on Rails apps.

Indeed it is in principle able to run Ruby on rails for an very simple example, but running any app requiring gems with native extensions (as e.g. nokogiri, which is required already by the rails default app, generated by rails new) already fails.

Part of the log looks like this:

libruby.so.2.4: cannot open shared object file: No such file or directory - /var/www/vhosts/hosting116285.a2f45.netcup.net/test.gcsb.info/vendor/bundle/ruby/2.4.0/gems/msgpack-1.2.6-x86_64-linux/lib/msgpack/msgpack.so (LoadError)
/var/www/vhosts/hosting116285.a2f45.netcup.net/test.gcsb.info/vendor/bundle/ruby/2.4.0/gems/msgpack-1.2.6-x86_64-linux/lib/msgpack.rb:11:in `require'
...
...
...
/var/www/vhosts/hosting116285.a2f45.netcup.net/test.gcsb.info/vendor/bundle/ruby/2.4.0/gems/rack-2.0.6/lib/rack/builder.rb:55:in `initialize'
config.ru:1:in `new'
config.ru:1:in `<main>'
/usr/share/passenger/helper-scripts/rack-preloader.rb:110:in `eval'
/usr/share/passenger/helper-scripts/rack-preloader.rb:110:in `preload_app'
/usr/share/passenger/helper-scripts/rack-preloader.rb:156:in `<module:App>'
/usr/share/passenger/helper-scripts/rack-preloader.rb:30:in `<module:PhusionPassenger>'
/usr/share/passenger/helper-scripts/rack-preloader.rb:29:in `<main>'

I have the options to "restart the app", "install packages" (which executes a bundle install --path vendor/bundle according to documentation), and "add environment variables".

I have very limited SSH access to the machine, but no access to any compilers or libraries.

The support advised me to "precompile my gems", but did not give any specific advice how to do this. I set up a virtual development machine which is somewhat near to the production machine, but definitely not yet the same in terms of all paths and e.g. Linux kernel version. I compiled the gems which supposedly works fine, put the precompiled gems in vendor/cache on the server, and install them via the Plesk interface.

However, the app keeps throwing errors. The pre-compiled gems (msgpack is one of them; originally with native extensions) seem to be linked in a wrong fashion? Maybe it's the linkage to ruby that it broken? I read about another (closed) issue where this linking is referenced?

Do you have an idea on this, or could you provide me with a "this is how we usually do it"-strategy? Do you intended gem-compiler to work for such a scenario, or am I misled? (If so, maybe we can update the Readme together?)

Thanks in advance! -- Robert

Method to execute commands/scripts before packaging the new gem

We need to provide pre-compiled gems without internet access. We download from rubygems.org, compile using gem compiler, and push the compiled gems to our local geminabox server. From here the gems are downloaded onto target machines that do not have compilers.

My problem is the passenger gem. This requires unpacking, then running a cmd which builds mod_passeneger.so. I want to include this file in the compiled gem.

gem compiler unpacks in a tmp area in a dir containing a time stamp - so I cannot unpack, compile & run gem compiler on the same dir hoping it will include mod_passenger.so.

Ideally I need a hook to be able to insert a file into the unpacked area before it is repackaged.

I can use gem unpack - but can I& place a file in the unpacked area and perhaps gem-compile the unpacked gem dir?

Or can I unpack & re-pack the gem-compiled gem file??

mysql2.so is not included in the packed gem

Mysql2 gemspec (https://github.com/brianmario/mysql2/blob/master/mysql2.gemspec) loads gem's files using git ls-files. However it has a .gitignore file (https://github.com/brianmario/mysql2/blob/master/.gitignore) that excludes compiled files, for instance *.so are excluded. In the compile method, after gathering the artifacts, we have to check if the .so files are still in the gemspec definition, if not, they must be reloaded. I am going to provide a patch with this implementation.

Sanity Checks for compiled gems would be lovely.

I'm playing around with gem-compiler to generate a binary version of therubyracer for x86_64-linux.

I generated the gem on a local virtual machine with a ruby compiled with rvm:

rvm reinstall 1.9.3 -C --enable-shared --enable-load-relative

Trying it out on heroku though generates the following error:

$ bundle exec ruby -rv8 -e 'puts V8::Context.new.eval("1+1")'
<internal:lib/rubygems/custom_require>:29:in `require': libruby.so.1.9: cannot open shared object file: No such file or directory - /app/vendor/bundle/ruby/1.9.1/gems/therubyracer-0.11.0beta3-x86_64-linux/lib/v8/init.so (LoadError)

full trace here: https://gist.github.com/c419ee5184ab3cb97b2e

I'm uncertain how to diagnose exactly what's going on here, but I suspect 'm doing something wrong here with the paths. Is there a way at all for gem-compiler to sanity check this?

gem compile test-4.0.6.gem

gem compile test-4.0.6.gem
Unpacking gem: 'test-4.0.6' in temporary directory...
ERROR: While executing gem ... (Gem::Compiler::CompilerError)
There are no extensions to build on this gem file.

Please help me...

Support for ruby 3.0

Hi @luislavena I tried installing gem-compiler (0.9.0) on ruby30 but it isn't working even after successful installation. I see a changes merged for support to ruby30 but i don't see latest version published. Can i please know if this change can be published anytime sooner as i was in need of this gem for my project. It works great with ruby27 or ruby26. So need it for ruby30 as well

[root@test-serverXXXX ~]# gem install gem-compiler
Successfully installed gem-compiler-0.9.0
Parsing documentation for gem-compiler-0.9.0
Done installing documentation for gem-compiler after 0 seconds
1 gem installed
[root@test-serverXXXX ~]#  gem list | grep gem-compiler
gem-compiler (0.9.0)
[root@test-serverXXXX ~]# gem help command
GEM commands are:

    build             Build a gem from a gemspec
    cert              Manage RubyGems certificates and signing settings
    check             Check a gem repository for added or missing files
    cleanup           Clean up old versions of installed gems
    contents          Display the contents of the installed gems
    dependency        Show the dependencies of an installed gem
    environment       Display information about the RubyGems environment
    fetch             Download a gem and place it in the current directory
    generate_index    Generates the index files for a gem server directory
    help              Provide help on the 'gem' command
    info              Show information for the given gem
    install           Install a gem into the local repository
    list              Display local gems whose name matches REGEXP
    lock              Generate a lockdown list of gems
    mirror            Mirror all gem files (requires rubygems-mirror)
    open              Open gem sources in editor
    outdated          Display all gems that need updates
    owner             Manage gem owners of a gem on the push server
    pristine          Restores installed gems to pristine condition from files
                      located in the gem cache
    push              Push a gem up to the gem server
    rdoc              Generates RDoc for pre-installed gems
    search            Display remote gems whose name matches REGEXP
    signin            Sign in to any gemcutter-compatible host. It defaults to
                      https://rubygems.org
    signout           Sign out from all the current sessions.
    sources           Manage the sources and cache file RubyGems uses to search
                      for gems
    specification     Display gem specification (in yaml)
    stale             List gems along with access times
    uninstall         Uninstall gems from the local repository
    unpack            Unpack an installed gem to the current directory
    update            Update installed gems to the latest version
    which             Find the location of a library file you can require
    yank              Remove a pushed gem from the index

For help on a particular command, use 'gem help COMMAND'.

Packaging gems with non-traditional extensions

Background

Over the years, several gems have decided to bundle certain dependencies within the source code in order to take advantage of certain functionality or simplify the gem development itself.

That is the case of gems like mosq and rabbitmq (and perhaps others) that use ffi as binding instead of MRI C extensions.

In the above examples, these gems uses Rakefile compile mechanism to build native shared libraries (ie. .so, .dll) binaries to be used later with FFI.

Because these shared libraries are not Ruby native extensions, it makes sense not to expose them to require lookup mechanism (ie. place them inside lib directory). The gem itself takes care of loading the extension via FFI and using it correctly.

However, since these shared libraries are not within the lookup directories detected by gem-compiler, those are not included in the package and thus, fail on usage.

While those are really not Ruby extensions, they are required to normal operation of these gems, which is why I believe they should be considered.

Proposal

Introduce an additional option to compile that, when used, allow us indicate the additional directory where it should look for build artifacts.

This option should be off by default, to avoid including build artifacts into the generated package.

When used, it should lookup for files with platform-specific extensions, collect them as artifacts and ensure are packaged in the final gem.

UI

$ gem compile mosq-0.2.3.gem --include-ext-dir=ext

Above will make gem-compiler lookup into ext directory for shared library artifacts.

Technical details

Determine platform-specific extensions for these artifacts similar how FFI does LIBSUFFIX:

  • darwin: dylib
  • linux, bsd, solaris: so
  • mingw, mswin, cygwin, msys: dll
  • Other: so

Use RbConfig::CONFIG['host_os'] as OS condition. Better use Gem::Platform.local instead.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.