rake-compiler 0.3.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,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