rake-compiler 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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