gem-compiler 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
@@ -0,0 +1,2 @@
1
+ require "rubygems/command_manager"
2
+ Gem::CommandManager.instance.register_command :compile
@@ -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: []