class FFI::Compiler::CompileTask
def add_define(name, value=1)
def add_define(name, value=1) @defines << "-D#{name}=#{value}" end
def add_include_path(path)
def add_include_path(path) @include_paths << path end
def cc
def cc @cc ||= (ENV['CC'] || RbConfig::CONFIG['CC'] || 'cc') end
def cxx
def cxx @cxx ||= (ENV['CXX'] || RbConfig::CONFIG['CXX'] || 'c++') end
def define_task!
def define_task! pic_flags = %w(-fPIC) so_flags = [] if @platform.mac? pic_flags = [] so_flags << '-bundle' elsif @platform.name =~ /linux/ so_flags << "-shared -Wl,-soname,#{lib_name}" else so_flags << '-shared' end so_flags = shelljoin(so_flags) out_dir = "#{@platform.arch}-#{@platform.os}" if @ext_dir != '.' out_dir = File.join(@ext_dir, out_dir) end directory(out_dir) CLOBBER.include(out_dir) lib_name = File.join(out_dir, Platform.system.map_library_name(@name)) iflags = @include_paths.uniq.map { |p| "-I#{p}" } @defines += @functions.uniq.map { |f| "-DHAVE_#{f.upcase}=1" } @defines += @headers.uniq.map { |h| "-DHAVE_#{h.upcase.sub(/\./, '_')}=1" } cflags = shelljoin(@cflags.to_a + pic_flags + iflags + @defines) cxxflags = shelljoin(@cxxflags.to_a + @cflags.to_a + pic_flags + iflags + @defines) ld_flags = shelljoin(@library_paths.map { |path| "-L#{path}" } + @ldflags.to_a) libs = shelljoin(@libraries.map { |l| "-l#{l}" } + @libs) src_files = [] obj_files = [] @source_dirs.each do |dir| files = FileList["#{dir}/**/*.{c,cpp}"] unless @exclude.empty? files.delete_if { |f| f =~ Regexp.union(*@exclude) } end src_files += files obj_files += files.ext('.o').map { |f| File.join(out_dir, f.sub(/^#{dir}\//, '')) } end index = 0 src_files.each do |src| obj_file = obj_files[index] if src =~ /\.c$/ file obj_file => [ src, File.dirname(obj_file) ] do |t| sh "#{cc} #{cflags} -o #{shellescape(t.name)} -c #{shellescape(t.prerequisites[0])}" end else file obj_file => [ src, File.dirname(obj_file) ] do |t| sh "#{cxx} #{cxxflags} -o #{shellescape(t.name)} -c #{shellescape(t.prerequisites[0])}" end end CLEAN.include(obj_file) index += 1 end ld = src_files.detect { |f| f =~ /\.cpp$/ } ? cxx : cc # create all the directories for the output files obj_files.map { |f| File.dirname(f) }.sort.uniq.map { |d| directory d } desc "Build dynamic library" MultiFileTask.define_task(lib_name => src_files + obj_files) do |t| objs = t.prerequisites.select { |file| file.end_with?('.o') } sh "#{ld} #{so_flags} -o #{shellescape(t.name)} #{shelljoin(objs)} #{ld_flags} #{libs}" end CLEAN.include(lib_name) @exports.each do |e| desc "Export #{e[:rb_file]}" file e[:header] => [ e[:rb_file] ] do |t| ruby "-I#{File.join(File.dirname(__FILE__), 'fake_ffi')} -I#{File.dirname(t.prerequisites[0])} #{File.join(File.dirname(__FILE__), 'exporter.rb')} #{shellescape(t.prerequisites[0])} #{shellescape(t.name)}" end obj_files.each { |o| file o => [ e[:header] ] } CLEAN.include(e[:header]) desc "Export API headers" task :api_headers => [ e[:header] ] end task :default => [ lib_name ] task :package => [ :api_headers ] end
def export(rb_file)
def export(rb_file) @exports << { :rb_file => rb_file, :header => File.join(@ext_dir, File.basename(rb_file).sub(/\.rb$/, '.h')) } end
def find_library(lib, func, *paths)
def find_library(lib, func, *paths) try_library(lib, function: func, paths: @library_paths) || try_library(libname, function: func, paths: paths) end
def have_func?(func)
def have_func?(func) main = <<-C_FILE extern void #{func}(); int main(int argc, char **argv) { #{func}(); return 0; } C_FILE if try_compile(main) @functions << func return true end false end
def have_header?(header, *paths)
def have_header?(header, *paths) try_header(header, @include_paths) || try_header(header, paths) end
def have_library(lib, func = nil, headers = nil, &b)
def have_library(lib, func = nil, headers = nil, &b) try_library(lib, function: func, headers: headers, paths: @library_paths) end
def have_library?(libname, *paths)
def have_library?(libname, *paths) try_library(libname, paths: @library_paths) || try_library(libname, paths: paths) end
def initialize(name)
def initialize(name) @name = File.basename(name) @ext_dir = File.dirname(name) @source_dirs = [@ext_dir] @exclude = [] @defines = [] @include_paths = [] @library_paths = [] @libraries = [] @headers = [] @functions = [] @cflags = Flags.new(shellsplit(ENV['CFLAGS']) || DEFAULT_CFLAGS.dup) @cxxflags = Flags.new(shellsplit(ENV['CXXFLAGS']) || DEFAULT_CFLAGS.dup) @ldflags = Flags.new(shellsplit(ENV['LDFLAGS']) || DEFAULT_LDFLAGS.dup) @libs = [] @platform = Platform.system @exports = [] yield self if block_given? define_task! end
def try_compile(src, *opts)
def try_compile(src, *opts) Dir.mktmpdir do |dir| path = File.join(dir, 'ffi-test.c') File.open(path, 'w') do |f| f << src end cflags = shelljoin(opts) output = File.join(dir, 'ffi-test') begin return system "#{cc} #{cflags} -o #{shellescape(output)} -c #{shellescape(path)} > #{shellescape(path)}.log 2>&1" rescue return false end end end
def try_header(header, paths)
def try_header(header, paths) main = <<-C_FILE #include <#{header}> int main(int argc, char **argv) { return 0; } C_FILE if paths.empty? && try_compile(main) @headers << header return true end paths.each do |path| if try_compile(main, "-I#{path}") @include_paths << path @headers << header return true end end false end
def try_library(libname, options = {})
def try_library(libname, options = {}) func = options[:function] || 'main' paths = options[:paths] || '' main = <<-C_FILE #{(options[:headers] || []).map {|h| "#include <#{h}>"}.join('\n')} extern int #{func}(); int main() { return #{func}(); } C_FILE if paths.empty? && try_compile(main) @libraries << libname return true end paths.each do |path| if try_compile(main, "-L#{path}", "-l#{libname}") @library_paths << path @libraries << libname end end end