gem-compiler 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +176 -0
- data/lib/rubygems/commands/compile_command.rb +31 -0
- data/lib/rubygems/compiler.rb +91 -0
- data/lib/rubygems_plugin.rb +2 -0
- data/rakefile.rb +32 -0
- metadata +56 -0
data/README.md
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
# gem-compiler
|
2
|
+
|
3
|
+
A RubyGems plugin that generates binary pre-compiled gems.
|
4
|
+
|
5
|
+
- [home](https://github.com/luislavena/gem-compiler)
|
6
|
+
- [bugs](https://github.com/luislavena/gem-compiler/issues)
|
7
|
+
|
8
|
+
## Description
|
9
|
+
|
10
|
+
`gem-compiler` is a RubyGems plugin that helps generates binary pre-compiled
|
11
|
+
gems from already existing ones without altering the original gem source
|
12
|
+
code. It is aimed at the pre-compilation of Ruby C extensions.
|
13
|
+
|
14
|
+
It uses an *outside-in* approach and leverages on existing RubyGems code to
|
15
|
+
do it.
|
16
|
+
|
17
|
+
The result of the compilation is a gem built for your current platform,
|
18
|
+
skipping the need of a compiler toolchain when installing it.
|
19
|
+
|
20
|
+
## Installation
|
21
|
+
|
22
|
+
To install gem-compiler you need to use RubyGems:
|
23
|
+
|
24
|
+
$ gem install gem-compiler
|
25
|
+
|
26
|
+
Which will fetch and install the plugin. After that the `compile` command
|
27
|
+
will be available through `gem`.
|
28
|
+
|
29
|
+
## Features
|
30
|
+
|
31
|
+
gem-compiler is a one trick pony. It adds a single command `compile` to
|
32
|
+
RubyGems.
|
33
|
+
|
34
|
+
Using that command, you can generate a binary from an existing gem.
|
35
|
+
|
36
|
+
## Usage
|
37
|
+
|
38
|
+
### Fetching a gem
|
39
|
+
|
40
|
+
If you don't have the gem locally, you can use `fetch` to retrieve it first:
|
41
|
+
|
42
|
+
$ gem fetch yajl-ruby --platform=ruby
|
43
|
+
Fetching: yajl-ruby-1.1.0.gem (100%)
|
44
|
+
Downloaded yajl-ruby-1.1.0
|
45
|
+
|
46
|
+
Please note that I was specific about which gem to fetch. This will avoid
|
47
|
+
RubyGems attempt to download any existing pre-compiled gem for my current
|
48
|
+
platform.
|
49
|
+
|
50
|
+
### Compiling a gem
|
51
|
+
|
52
|
+
You need to tell RubyGems the filename of the gem you want to compile:
|
53
|
+
|
54
|
+
$ gem compile yajl-ruby-1.1.0.gem
|
55
|
+
|
56
|
+
The above command will unpack, compile any existing extensions found and
|
57
|
+
generate a pre-compiled binary:
|
58
|
+
|
59
|
+
Unpacking gem: 'yajl-ruby-1.1.0' in temporary directory...
|
60
|
+
Building native extensions. This could take a while...
|
61
|
+
Successfully built RubyGem
|
62
|
+
Name: yajl-ruby
|
63
|
+
Version: 1.1.0
|
64
|
+
File: yajl-ruby-1.1.0-x86-mingw32.gem
|
65
|
+
|
66
|
+
You can now simply install the pre-compiled gem and it will not trigger any
|
67
|
+
build process:
|
68
|
+
|
69
|
+
C:\> gem install --local yajl-ruby-1.1.0-x86-mingw32.gem
|
70
|
+
Successfully installed yajl-ruby-1.1.0-x86-mingw32
|
71
|
+
1 gem installed
|
72
|
+
|
73
|
+
### Compiling from Rake
|
74
|
+
|
75
|
+
Most of the times, as gem developer, you would like to genrate both kind of
|
76
|
+
gems at once. For that purpose, you can add a task for Rake similar to the
|
77
|
+
one below:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
desc "Generate a pre-compiled native gem"
|
81
|
+
task "gem:native" => ["gem"] do
|
82
|
+
sh "gem compile #{gem_file}"
|
83
|
+
end
|
84
|
+
```
|
85
|
+
|
86
|
+
Of couse, that assusems you have a task `gem` that generates the gem needed
|
87
|
+
by this task.
|
88
|
+
|
89
|
+
## Requirements
|
90
|
+
|
91
|
+
### Ruby and RubyGems
|
92
|
+
|
93
|
+
It's assumed you have Ruby and RubyGems installed. gem-compiler requires
|
94
|
+
RubyGems 1.8.x to properly work.
|
95
|
+
|
96
|
+
If you don't have RubyGems 1.8.x, you can upgrade by running:
|
97
|
+
|
98
|
+
$ gem update --system
|
99
|
+
|
100
|
+
### A compiler
|
101
|
+
|
102
|
+
In order to compile a gem, you need a compiler toolchain installed. Depending
|
103
|
+
on your Operating System you will have one already installed or will require
|
104
|
+
additional steps to do it. Check your OS documentation about getting the
|
105
|
+
right one.
|
106
|
+
|
107
|
+
### If you're using Windows
|
108
|
+
|
109
|
+
For those using RubyInstaller-based builds, you will need to download the
|
110
|
+
DevKit from our [downloads page](http://rubyinstaller.org/downloads) and
|
111
|
+
follow the [installation instructions](https://github.com/oneclick/rubyinstaller/wiki/Development-Kit).
|
112
|
+
|
113
|
+
To be sure your installation of Ruby is based on RubyInstaller, execute at
|
114
|
+
the command prompt:
|
115
|
+
|
116
|
+
C:\> ruby --version
|
117
|
+
|
118
|
+
And from the output:
|
119
|
+
|
120
|
+
tcs-ruby 1.9.3p196 (2012-04-21, TCS patched 2012-04-21) [i386-mingw32]
|
121
|
+
|
122
|
+
If you see `mingw32`, that means you're using a RubyInstaller build
|
123
|
+
(MinGW based).
|
124
|
+
|
125
|
+
## Differences with rake-compiler
|
126
|
+
|
127
|
+
[rake-compiler](https://github.com/luislavena/rake-compiler) has provided to
|
128
|
+
Ruby library authors a *tool* for compiling extensions and generating binary
|
129
|
+
gems of their libraries.
|
130
|
+
|
131
|
+
You can consider rake-compiler's approach be an *inside-out* process. To do
|
132
|
+
its magic, it requires library authors to modify their source code, adjust
|
133
|
+
some structure and learn a series of commands.
|
134
|
+
|
135
|
+
While the ideal scenario is using a tool like rake-compiler that endorses
|
136
|
+
*convention over configuration*, is not humanly possible change all the
|
137
|
+
projects by snapping your fingers :wink:
|
138
|
+
|
139
|
+
## What is missing
|
140
|
+
|
141
|
+
The following are the list of features I would like to implement at some
|
142
|
+
point:
|
143
|
+
|
144
|
+
* Cross compile gems to any platform that Ruby can run
|
145
|
+
(e.g. from Linux/OSX to Windows, x86 to x64, x86 Linux to ARM Linux, etc)
|
146
|
+
|
147
|
+
* Create multiple gems from the same build
|
148
|
+
(e.g. target both x86-mswin32-60 and x86-mingw32)
|
149
|
+
|
150
|
+
* Ability to build fat-binaries targeting both Ruby 1.8 and 1.9.x,
|
151
|
+
placing automatic stubs to handle extension loading.
|
152
|
+
|
153
|
+
## License
|
154
|
+
|
155
|
+
(The MIT License)
|
156
|
+
|
157
|
+
Copyright (c) Luis Lavena
|
158
|
+
|
159
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
160
|
+
a copy of this software and associated documentation files (the
|
161
|
+
'Software'), to deal in the Software without restriction, including
|
162
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
163
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
164
|
+
permit persons to whom the Software is furnished to do so, subject to
|
165
|
+
the following conditions:
|
166
|
+
|
167
|
+
The above copyright notice and this permission notice shall be
|
168
|
+
included in all copies or substantial portions of the Software.
|
169
|
+
|
170
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
171
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
172
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
173
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
174
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
175
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
176
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "rubygems/command"
|
2
|
+
|
3
|
+
class Gem::Commands::CompileCommand < Gem::Command
|
4
|
+
def initialize
|
5
|
+
super "compile", "Create binary pre-compiled gem",
|
6
|
+
:output => Dir.pwd
|
7
|
+
end
|
8
|
+
|
9
|
+
def arguments
|
10
|
+
"GEMFILE path to the gem file to compile"
|
11
|
+
end
|
12
|
+
|
13
|
+
def usage
|
14
|
+
"#{program_name} GEMFILE"
|
15
|
+
end
|
16
|
+
|
17
|
+
def execute
|
18
|
+
gemfile = options[:args].shift
|
19
|
+
|
20
|
+
# no gem, no binary
|
21
|
+
unless gemfile
|
22
|
+
raise Gem::CommandLineError,
|
23
|
+
"Please specify a gem file on the command line (e.g. #{program_name} foo-0.1.0.gem"
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems/compiler"
|
27
|
+
|
28
|
+
compiler = Gem::Compiler.new(gemfile, options[:output])
|
29
|
+
compiler.compile
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require "rbconfig"
|
2
|
+
require "tmpdir"
|
3
|
+
require "rubygems/installer"
|
4
|
+
require "rubygems/builder"
|
5
|
+
require "fileutils"
|
6
|
+
|
7
|
+
class Gem::Compiler
|
8
|
+
include Gem::UserInteraction
|
9
|
+
|
10
|
+
# raise when there is a error
|
11
|
+
class CompilerError < Gem::InstallError; end
|
12
|
+
|
13
|
+
def initialize(gemfile, output_dir)
|
14
|
+
@gemfile = gemfile
|
15
|
+
@output_dir = output_dir
|
16
|
+
end
|
17
|
+
|
18
|
+
def compile
|
19
|
+
installer = Gem::Installer.new(@gemfile, :unpack => true)
|
20
|
+
|
21
|
+
# Hmm, gem already compiled?
|
22
|
+
if installer.spec.platform != Gem::Platform::RUBY
|
23
|
+
raise CompilerError,
|
24
|
+
"The gem file seems to be compiled already."
|
25
|
+
end
|
26
|
+
|
27
|
+
# Hmm, no extensions?
|
28
|
+
if installer.spec.extensions.empty?
|
29
|
+
raise CompilerError,
|
30
|
+
"There are no extensions to build on this gem file."
|
31
|
+
end
|
32
|
+
|
33
|
+
tmpdir = Dir.mktmpdir
|
34
|
+
basename = File.basename(@gemfile, '.gem')
|
35
|
+
target_dir = File.join(tmpdir, basename)
|
36
|
+
|
37
|
+
# unpack gem sources into target_dir
|
38
|
+
# We need the basename to keep the unpack happy
|
39
|
+
say "Unpacking gem: '#{basename}' in temporary directory..." if Gem.configuration.verbose
|
40
|
+
installer.unpack(target_dir)
|
41
|
+
|
42
|
+
# build extensions
|
43
|
+
installer.build_extensions
|
44
|
+
|
45
|
+
# determine build artifacts from require_paths
|
46
|
+
dlext = RbConfig::CONFIG["DLEXT"]
|
47
|
+
lib_dirs = installer.spec.require_paths.join(",")
|
48
|
+
|
49
|
+
artifacts = Dir.glob("#{target_dir}/{#{lib_dirs}}/**/*.#{dlext}")
|
50
|
+
|
51
|
+
# build a new gemspec from the original one
|
52
|
+
gemspec = installer.spec.dup
|
53
|
+
|
54
|
+
# add discovered artifacts
|
55
|
+
artifacts.each do |path|
|
56
|
+
# path needs to be relative to target_dir
|
57
|
+
file = path.gsub("#{target_dir}/", "")
|
58
|
+
|
59
|
+
say "Adding '#{file}' to gemspec" if Gem.configuration.really_verbose
|
60
|
+
gemspec.files.push file
|
61
|
+
end
|
62
|
+
|
63
|
+
# clear out extensions from gemspec
|
64
|
+
gemspec.extensions.clear
|
65
|
+
|
66
|
+
# adjust platform
|
67
|
+
gemspec.platform = Gem::Platform::CURRENT
|
68
|
+
|
69
|
+
# build new gem
|
70
|
+
output_gem = nil
|
71
|
+
|
72
|
+
Dir.chdir target_dir do
|
73
|
+
builder = Gem::Builder.new(gemspec)
|
74
|
+
output_gem = builder.build
|
75
|
+
end
|
76
|
+
|
77
|
+
unless output_gem
|
78
|
+
raise CompilerError,
|
79
|
+
"There was a problem building the gem."
|
80
|
+
end
|
81
|
+
|
82
|
+
# move the built gem to the original output directory
|
83
|
+
FileUtils.mv File.join(target_dir, output_gem), @output_dir
|
84
|
+
|
85
|
+
# cleanup
|
86
|
+
FileUtils.rm_rf tmpdir
|
87
|
+
|
88
|
+
# return the path of the gem
|
89
|
+
output_gem
|
90
|
+
end
|
91
|
+
end
|
data/rakefile.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
require "rubygems/package_task"
|
2
|
+
|
3
|
+
gemspec = Gem::Specification.new do |s|
|
4
|
+
# basic
|
5
|
+
s.name = "gem-compiler"
|
6
|
+
s.version = "0.1.0"
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
|
9
|
+
# description
|
10
|
+
s.summary = "A RubyGems plugin that generates binary pre-compiled gems."
|
11
|
+
s.description = <<-EOF
|
12
|
+
A RubyGems plugin that helps generates binary pre-compiled gems from
|
13
|
+
already existing ones without altering the original gem source code.
|
14
|
+
It is aimed at the pre-compilation of Ruby C extensions.
|
15
|
+
EOF
|
16
|
+
|
17
|
+
# project info
|
18
|
+
s.homepage = "https://github.com/luislavena/gem-compiler"
|
19
|
+
s.licenses = ["MIT"]
|
20
|
+
s.author = "Luis Lavena"
|
21
|
+
s.email = "luislavena@gmail.com"
|
22
|
+
|
23
|
+
# requirements
|
24
|
+
s.required_ruby_version = ">= 1.9.3"
|
25
|
+
s.required_rubygems_version = ">= 1.8.24"
|
26
|
+
|
27
|
+
# boring part
|
28
|
+
s.files = FileList["README.md", "rakefile.rb", "lib/**/*.rb"]
|
29
|
+
end
|
30
|
+
|
31
|
+
Gem::PackageTask.new(gemspec) do |pkg|
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gem-compiler
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Luis Lavena
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-05-06 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: ! 'A RubyGems plugin that helps generates binary pre-compiled gems from
|
15
|
+
|
16
|
+
already existing ones without altering the original gem source code.
|
17
|
+
|
18
|
+
It is aimed at the pre-compilation of Ruby C extensions.
|
19
|
+
|
20
|
+
'
|
21
|
+
email: luislavena@gmail.com
|
22
|
+
executables: []
|
23
|
+
extensions: []
|
24
|
+
extra_rdoc_files: []
|
25
|
+
files:
|
26
|
+
- README.md
|
27
|
+
- rakefile.rb
|
28
|
+
- lib/rubygems/commands/compile_command.rb
|
29
|
+
- lib/rubygems/compiler.rb
|
30
|
+
- lib/rubygems_plugin.rb
|
31
|
+
homepage: https://github.com/luislavena/gem-compiler
|
32
|
+
licenses:
|
33
|
+
- MIT
|
34
|
+
post_install_message:
|
35
|
+
rdoc_options: []
|
36
|
+
require_paths:
|
37
|
+
- lib
|
38
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 1.9.3
|
44
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
45
|
+
none: false
|
46
|
+
requirements:
|
47
|
+
- - ! '>='
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: 1.8.24
|
50
|
+
requirements: []
|
51
|
+
rubyforge_project:
|
52
|
+
rubygems_version: 1.8.24
|
53
|
+
signing_key:
|
54
|
+
specification_version: 3
|
55
|
+
summary: A RubyGems plugin that generates binary pre-compiled gems.
|
56
|
+
test_files: []
|