rake-compiler 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/History.txt +32 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +258 -0
- data/Rakefile +23 -0
- data/bin/rake-compiler +24 -0
- data/cucumber.yml +1 -0
- data/features/compile.feature +71 -0
- data/features/cross-compile.feature +13 -0
- data/features/cross-package.feature +14 -0
- data/features/package.feature +40 -0
- data/features/step_definitions/compilation.rb +43 -0
- data/features/step_definitions/cross_compilation.rb +21 -0
- data/features/step_definitions/execution.rb +30 -0
- data/features/step_definitions/folders.rb +32 -0
- data/features/step_definitions/gem.rb +29 -0
- data/features/support/env.rb +7 -0
- data/features/support/file_templates.rb +88 -0
- data/features/support/generators.rb +77 -0
- data/lib/rake/extensiontask.rb +270 -0
- data/spec/lib/rake/extensiontask_spec.rb +352 -0
- data/spec/spec_helper.rb +9 -0
- data/tasks/bin/cross-ruby.rake +159 -0
- data/tasks/common.rake +13 -0
- data/tasks/cucumber.rake +14 -0
- data/tasks/gem.rake +59 -0
- data/tasks/rdoc.rake +9 -0
- data/tasks/release.rake +79 -0
- data/tasks/rspec.rake +33 -0
- metadata +97 -0
@@ -0,0 +1,88 @@
|
|
1
|
+
def template_rakefile
|
2
|
+
<<-EOF
|
3
|
+
# add rake-compiler lib dir to the LOAD_PATH
|
4
|
+
$LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__), '../..', 'lib'))
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require 'rake'
|
8
|
+
|
9
|
+
# load rakefile extensions (tasks)
|
10
|
+
Dir['tasks/*.rake'].each { |f| import f }
|
11
|
+
EOF
|
12
|
+
end
|
13
|
+
|
14
|
+
def template_rake_gemspec(gem_name)
|
15
|
+
<<-EOF
|
16
|
+
require 'rake/gempackagetask'
|
17
|
+
SPEC = Gem::Specification.new do |s|
|
18
|
+
s.name = "#{gem_name}"
|
19
|
+
s.version = "0.1.0"
|
20
|
+
s.summary = "#{gem_name} test gem for rake-compiler"
|
21
|
+
|
22
|
+
s.files = FileList["ext/**/*.{rb,c,h}", "Rakefile", "tasks/*.rake", "lib/**/*.rb"]
|
23
|
+
|
24
|
+
s.extensions = FileList["ext/**/extconf.rb"]
|
25
|
+
|
26
|
+
s.has_rdoc = true
|
27
|
+
|
28
|
+
s.homepage = 'http://github.com/luislavena/rake-compiler'
|
29
|
+
s.rubyforge_project = 'TODO'
|
30
|
+
|
31
|
+
s.authors = ["Luis Lavena"]
|
32
|
+
s.email = ["luislavena@gmail.com"]
|
33
|
+
end
|
34
|
+
|
35
|
+
gem_package = Rake::GemPackageTask.new(SPEC) do |pkg|
|
36
|
+
pkg.need_zip = false
|
37
|
+
pkg.need_tar = false
|
38
|
+
end
|
39
|
+
EOF
|
40
|
+
end
|
41
|
+
|
42
|
+
def template_rake_extension(extension_name, gem_spec = nil)
|
43
|
+
<<-EOF
|
44
|
+
require 'rake/extensiontask'
|
45
|
+
Rake::ExtensionTask.new("#{extension_name}"#{', SPEC' if gem_spec})
|
46
|
+
EOF
|
47
|
+
end
|
48
|
+
|
49
|
+
def template_rake_extension_with_platform(extension_name, platform)
|
50
|
+
<<-EOF
|
51
|
+
require 'rake/extensiontask'
|
52
|
+
Rake::ExtensionTask.new("#{extension_name}", SPEC) do |ext|
|
53
|
+
ext.platform = "#{platform}"
|
54
|
+
end
|
55
|
+
EOF
|
56
|
+
end
|
57
|
+
|
58
|
+
def template_rake_extension_cross_compile(extension_name, gem_spec = nil)
|
59
|
+
<<-EOF
|
60
|
+
require 'rake/extensiontask'
|
61
|
+
Rake::ExtensionTask.new("#{extension_name}"#{', SPEC' if gem_spec}) do |ext|
|
62
|
+
ext.cross_compile = true
|
63
|
+
end
|
64
|
+
EOF
|
65
|
+
end
|
66
|
+
|
67
|
+
def template_extconf(extension_name)
|
68
|
+
<<-EOF
|
69
|
+
require 'mkmf'
|
70
|
+
create_makefile("#{extension_name}")
|
71
|
+
EOF
|
72
|
+
end
|
73
|
+
|
74
|
+
def template_source_c(extension_name)
|
75
|
+
<<-EOF
|
76
|
+
#include "source.h"
|
77
|
+
void Init_#{extension_name}()
|
78
|
+
{
|
79
|
+
printf("source.c of extension #{extension_name}\\n");
|
80
|
+
}
|
81
|
+
EOF
|
82
|
+
end
|
83
|
+
|
84
|
+
def template_source_h
|
85
|
+
<<-EOF
|
86
|
+
#include "ruby.h"
|
87
|
+
EOF
|
88
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
def generate_scaffold_structure
|
2
|
+
# create folder structure
|
3
|
+
FileUtils.mkdir_p "lib"
|
4
|
+
FileUtils.mkdir_p "tasks"
|
5
|
+
FileUtils.mkdir_p "tmp"
|
6
|
+
|
7
|
+
# create Rakefile loader
|
8
|
+
File.open("Rakefile", 'w') do |rakefile|
|
9
|
+
rakefile.puts template_rakefile.strip
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def generate_gem_task(gem_name)
|
14
|
+
# create generic gem task
|
15
|
+
File.open("tasks/gem.rake", 'w') do |gem_rake|
|
16
|
+
gem_rake.puts template_rake_gemspec(gem_name)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def generate_extension_task_for(extension_name, platform = nil)
|
21
|
+
# create folder structure
|
22
|
+
FileUtils.mkdir_p "ext/#{extension_name}"
|
23
|
+
|
24
|
+
return if File.exist?("tasks/#{extension_name}.rake")
|
25
|
+
|
26
|
+
# Building a gem?
|
27
|
+
if File.exist?("tasks/gem.rake") then
|
28
|
+
File.open("tasks/gem.rake", 'a+') do |ext_in_gem|
|
29
|
+
if platform
|
30
|
+
ext_in_gem.puts template_rake_extension_with_platform(extension_name, platform)
|
31
|
+
else
|
32
|
+
ext_in_gem.puts template_rake_extension(extension_name, true)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
else
|
36
|
+
# create specific extension rakefile
|
37
|
+
File.open("tasks/#{extension_name}.rake", 'w') do |ext_rake|
|
38
|
+
ext_rake.puts template_rake_extension(extension_name)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def generate_cross_compile_extension_task_for(extension_name)
|
44
|
+
# create folder structure
|
45
|
+
FileUtils.mkdir_p "ext/#{extension_name}"
|
46
|
+
|
47
|
+
return if File.exist?("tasks/#{extension_name}.rake")
|
48
|
+
|
49
|
+
# create specific extension rakefile
|
50
|
+
# Building a gem?
|
51
|
+
if File.exist?("tasks/gem.rake") then
|
52
|
+
File.open("tasks/gem.rake", 'a+') do |ext_in_gem|
|
53
|
+
ext_in_gem.puts template_rake_extension_cross_compile(extension_name, true)
|
54
|
+
end
|
55
|
+
else
|
56
|
+
File.open("tasks/#{extension_name}.rake", 'w') do |ext_rake|
|
57
|
+
ext_rake.puts template_rake_extension_cross_compile(extension_name)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def generate_source_code_for(extension_name)
|
63
|
+
# source C file
|
64
|
+
File.open("ext/#{extension_name}/source.c", 'w') do |c|
|
65
|
+
c.puts template_source_c(extension_name)
|
66
|
+
end
|
67
|
+
|
68
|
+
# header H file
|
69
|
+
File.open("ext/#{extension_name}/source.h", 'w') do |h|
|
70
|
+
h.puts template_source_h
|
71
|
+
end
|
72
|
+
|
73
|
+
# extconf.rb file
|
74
|
+
File.open("ext/#{extension_name}/extconf.rb", 'w') do |ext|
|
75
|
+
ext.puts template_extconf(extension_name)
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,270 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Define a series of tasks to aid in the compilation of C extensions for
|
4
|
+
# gem developer/creators.
|
5
|
+
|
6
|
+
require 'rake'
|
7
|
+
require 'rake/clean'
|
8
|
+
require 'rake/tasklib'
|
9
|
+
require 'rbconfig'
|
10
|
+
|
11
|
+
module Rake
|
12
|
+
autoload :GemPackageTask, 'rake/gempackagetask'
|
13
|
+
autoload :YAML, 'yaml'
|
14
|
+
|
15
|
+
class ExtensionTask < TaskLib
|
16
|
+
attr_accessor :name
|
17
|
+
attr_accessor :gem_spec
|
18
|
+
attr_accessor :config_script
|
19
|
+
attr_accessor :tmp_dir
|
20
|
+
attr_accessor :ext_dir
|
21
|
+
attr_accessor :lib_dir
|
22
|
+
attr_accessor :platform
|
23
|
+
attr_accessor :config_options
|
24
|
+
attr_accessor :source_pattern
|
25
|
+
attr_accessor :cross_compile
|
26
|
+
attr_accessor :cross_platform
|
27
|
+
attr_accessor :cross_config_options
|
28
|
+
|
29
|
+
def initialize(name = nil, gem_spec = nil)
|
30
|
+
init(name, gem_spec)
|
31
|
+
yield self if block_given?
|
32
|
+
define
|
33
|
+
end
|
34
|
+
|
35
|
+
def init(name = nil, gem_spec = nil)
|
36
|
+
@name = name
|
37
|
+
@gem_spec = gem_spec
|
38
|
+
@config_script = 'extconf.rb'
|
39
|
+
@tmp_dir = 'tmp'
|
40
|
+
@ext_dir = 'ext'
|
41
|
+
@lib_dir = 'lib'
|
42
|
+
@source_pattern = "*.c"
|
43
|
+
@config_options = []
|
44
|
+
@cross_compile = false
|
45
|
+
@cross_config_options = []
|
46
|
+
end
|
47
|
+
|
48
|
+
def platform
|
49
|
+
@platform ||= RUBY_PLATFORM
|
50
|
+
end
|
51
|
+
|
52
|
+
def cross_platform
|
53
|
+
@cross_platform ||= 'i386-mingw32'
|
54
|
+
end
|
55
|
+
|
56
|
+
def define
|
57
|
+
fail "Extension name must be provided." if @name.nil?
|
58
|
+
|
59
|
+
define_compile_tasks
|
60
|
+
|
61
|
+
# only gems with 'ruby' platforms are allowed to define native tasks
|
62
|
+
define_native_tasks if @gem_spec && @gem_spec.platform == 'ruby'
|
63
|
+
|
64
|
+
# only define cross platform functionality when enabled
|
65
|
+
# FIXME: there is no value for having this on Windows or JRuby
|
66
|
+
define_cross_platform_tasks if @cross_compile
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
def define_compile_tasks(for_platform = nil)
|
71
|
+
# platform usage
|
72
|
+
platf = for_platform || platform
|
73
|
+
|
74
|
+
# tmp_path
|
75
|
+
tmp_path = "#{@tmp_dir}/#{platf}/#{@name}"
|
76
|
+
|
77
|
+
# cleanup and clobbering
|
78
|
+
CLEAN.include(tmp_path)
|
79
|
+
CLOBBER.include("#{@lib_dir}/#{binary}")
|
80
|
+
CLOBBER.include("#{@tmp_dir}")
|
81
|
+
|
82
|
+
# directories we need
|
83
|
+
directory tmp_path
|
84
|
+
directory lib_dir
|
85
|
+
|
86
|
+
# copy binary from temporary location to final lib
|
87
|
+
# tmp/extension_name/extension_name.{so,bundle} => lib/
|
88
|
+
task "copy:#{@name}:#{platf}" => [lib_dir, "#{tmp_path}/#{binary}"] do
|
89
|
+
cp "#{tmp_path}/#{binary}", "#{@lib_dir}/#{binary}"
|
90
|
+
end
|
91
|
+
|
92
|
+
# binary in temporary folder depends on makefile and source files
|
93
|
+
# tmp/extension_name/extension_name.{so,bundle}
|
94
|
+
file "#{tmp_path}/#{binary}" => ["#{tmp_path}/Makefile"] + source_files do
|
95
|
+
chdir tmp_path do
|
96
|
+
sh make
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
# makefile depends of tmp_dir and config_script
|
101
|
+
# tmp/extension_name/Makefile
|
102
|
+
file "#{tmp_path}/Makefile" => [tmp_path, extconf] do |t|
|
103
|
+
options = @config_options.dup
|
104
|
+
|
105
|
+
# rbconfig.rb will be present if we are cross compiling
|
106
|
+
if t.prerequisites.include?("#{tmp_path}/rbconfig.rb") then
|
107
|
+
options.push(*@cross_config_options)
|
108
|
+
end
|
109
|
+
|
110
|
+
parent = Dir.pwd
|
111
|
+
chdir tmp_path do
|
112
|
+
# FIXME: Rake is broken for multiple arguments system() calls.
|
113
|
+
# Add current directory to the search path of Ruby
|
114
|
+
# Also, include additional parameters supplied.
|
115
|
+
ruby ['-I.', File.join(parent, extconf), *options].join(' ')
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# compile tasks
|
120
|
+
unless Rake::Task.task_defined?('compile') then
|
121
|
+
desc "Compile all the extensions"
|
122
|
+
task "compile"
|
123
|
+
end
|
124
|
+
|
125
|
+
# compile:name
|
126
|
+
unless Rake::Task.task_defined?("compile:#{@name}") then
|
127
|
+
desc "Compile #{@name}"
|
128
|
+
task "compile:#{@name}"
|
129
|
+
end
|
130
|
+
|
131
|
+
# Allow segmented compilation by platfrom (open door for 'cross compile')
|
132
|
+
task "compile:#{@name}:#{platf}" => ["copy:#{@name}:#{platf}"]
|
133
|
+
task "compile:#{platf}" => ["compile:#{@name}:#{platf}"]
|
134
|
+
|
135
|
+
# Only add this extension to the compile chain if current
|
136
|
+
# platform matches the indicated one.
|
137
|
+
if platf == RUBY_PLATFORM then
|
138
|
+
# ensure file is always copied
|
139
|
+
file "#{@lib_dir}/#{binary}" => ["copy:#{name}:#{platf}"]
|
140
|
+
|
141
|
+
task "compile:#{@name}" => ["compile:#{@name}:#{platf}"]
|
142
|
+
task "compile" => ["compile:#{platf}"]
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def define_native_tasks(for_platform = nil)
|
147
|
+
platf = for_platform || platform
|
148
|
+
|
149
|
+
# tmp_path
|
150
|
+
tmp_path = "#{@tmp_dir}/#{platf}/#{@name}"
|
151
|
+
|
152
|
+
# create 'native:gem_name' and chain it to 'native' task
|
153
|
+
spec = @gem_spec.dup
|
154
|
+
|
155
|
+
unless Rake::Task.task_defined?("native:#{@gem_spec.name}:#{platf}")
|
156
|
+
task "native:#{@gem_spec.name}:#{platf}" do |t|
|
157
|
+
# adjust to specified platform
|
158
|
+
spec.platform = platf
|
159
|
+
|
160
|
+
# clear the extensions defined in the specs
|
161
|
+
spec.extensions.clear
|
162
|
+
|
163
|
+
# add the binaries that this task depends on
|
164
|
+
# ensure the files get properly copied to lib_dir
|
165
|
+
ext_files = t.prerequisites.map { |ext| "#{@lib_dir}/#{File.basename(ext)}" }
|
166
|
+
ext_files.each do |ext|
|
167
|
+
unless Rake::Task.task_defined?("#{@lib_dir}/#{File.basename(ext)}") then
|
168
|
+
# strip out path and .so/.bundle
|
169
|
+
file "#{@lib_dir}/#{File.basename(ext)}" => ["copy:#{File.basename(ext).ext('')}:#{platf}"]
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# include the files in the gem specification
|
174
|
+
spec.files += ext_files
|
175
|
+
|
176
|
+
# Generate a package for this gem
|
177
|
+
gem_package = Rake::GemPackageTask.new(spec) do |pkg|
|
178
|
+
pkg.need_zip = false
|
179
|
+
pkg.need_tar = false
|
180
|
+
end
|
181
|
+
|
182
|
+
# ensure the binaries are copied
|
183
|
+
task "#{gem_package.package_dir}/#{gem_package.gem_file}" => ["copy:#{@name}:#{platf}"]
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
# add binaries to the dependency chain
|
188
|
+
task "native:#{@gem_spec.name}:#{platf}" => ["#{tmp_path}/#{binary}"]
|
189
|
+
|
190
|
+
# Allow segmented packaging by platfrom (open door for 'cross compile')
|
191
|
+
task "native:#{platf}" => ["native:#{@gem_spec.name}:#{platf}"]
|
192
|
+
|
193
|
+
# Only add this extension to the compile chain if current
|
194
|
+
# platform matches the indicated one.
|
195
|
+
if platf == RUBY_PLATFORM then
|
196
|
+
task "native:#{@gem_spec.name}" => ["native:#{@gem_spec.name}:#{platf}"]
|
197
|
+
task "native" => ["native:#{platf}"]
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def define_cross_platform_tasks
|
202
|
+
config_path = File.expand_path("~/.rake-compiler/config.yml")
|
203
|
+
major_ver = (ENV['RUBY_CC_VERSION'] || RUBY_VERSION).match(/(\d+.\d+)/)[1]
|
204
|
+
|
205
|
+
# warn the user about the need of configuration to use cross compilation.
|
206
|
+
unless File.exist?(config_path)
|
207
|
+
warn "rake-compiler must be configured first to enable cross-compilation"
|
208
|
+
return
|
209
|
+
end
|
210
|
+
|
211
|
+
config_file = YAML.load_file(config_path)
|
212
|
+
|
213
|
+
# tmp_path
|
214
|
+
tmp_path = "#{@tmp_dir}/#{cross_platform}/#{@name}"
|
215
|
+
|
216
|
+
unless rbconfig_file = config_file["rbconfig-#{major_ver}"] then
|
217
|
+
fail "no configuration section for this version of Ruby (rbconfig-#{major_ver})"
|
218
|
+
end
|
219
|
+
|
220
|
+
# define compilation tasks for cross platfrom!
|
221
|
+
define_compile_tasks(cross_platform)
|
222
|
+
|
223
|
+
# chain rbconfig.rb to Makefile generation
|
224
|
+
file "#{tmp_path}/Makefile" => ["#{tmp_path}/rbconfig.rb"]
|
225
|
+
|
226
|
+
# copy the file from the cross-ruby location
|
227
|
+
file "#{tmp_path}/rbconfig.rb" => [rbconfig_file] do |t|
|
228
|
+
cp t.prerequisites.first, t.name
|
229
|
+
end
|
230
|
+
|
231
|
+
# now define native tasks for cross compiled files
|
232
|
+
define_native_tasks(cross_platform) if @gem_spec && @gem_spec.platform == 'ruby'
|
233
|
+
|
234
|
+
# create cross task
|
235
|
+
task 'cross' do
|
236
|
+
# clear compile dependencies
|
237
|
+
Rake::Task['compile'].prerequisites.clear
|
238
|
+
|
239
|
+
# chain the cross platform ones
|
240
|
+
task 'compile' => ["compile:#{cross_platform}"]
|
241
|
+
|
242
|
+
# clear lib/binary dependencies and trigger cross platform ones
|
243
|
+
Rake::Task["#{@lib_dir}/#{binary}"].prerequisites.clear
|
244
|
+
file "#{@lib_dir}/#{binary}" => ["copy:#{@name}:#{cross_platform}"]
|
245
|
+
|
246
|
+
# if everything for native task is in place
|
247
|
+
if @gem_spec && @gem_spec.platform == 'ruby' then
|
248
|
+
Rake::Task['native'].prerequisites.clear
|
249
|
+
task 'native' => ["native:#{cross_platform}"]
|
250
|
+
end
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
def extconf
|
255
|
+
"#{@ext_dir}/#{@name}/#{@config_script}"
|
256
|
+
end
|
257
|
+
|
258
|
+
def make
|
259
|
+
RUBY_PLATFORM =~ /mswin/ ? 'nmake' : 'make'
|
260
|
+
end
|
261
|
+
|
262
|
+
def binary
|
263
|
+
"#{@name}.#{RbConfig::CONFIG['DLEXT']}"
|
264
|
+
end
|
265
|
+
|
266
|
+
def source_files
|
267
|
+
@source_files ||= FileList["#{@ext_dir}/#{@name}/#{@source_pattern}"]
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|