class FFI::Compiler::CompileTask

Attributes

cflags[R]
cxxflags[R]
ldflags[R]
libs[R]
platform[R]

Public Class Methods

new(name) { |self| ... } click to toggle source
# File lib/ffi-compiler/compile_task.rb, line 17
def initialize(name)
  @name = File.basename(name)
  @ext_dir = File.dirname(name)
  @defines = []
  @include_paths = []
  @library_paths = []
  @libraries = []
  @headers = []
  @functions = []
  @cflags = DEFAULT_CFLAGS.dup
  @cxxflags = DEFAULT_CFLAGS.dup
  @ldflags = DEFAULT_LDFLAGS.dup
  @libs = []
  @platform = Platform.system
  @exports = []

  yield self if block_given?
  define_task!
end

Public Instance Methods

export(rb_file) click to toggle source
# File lib/ffi-compiler/compile_task.rb, line 66
def export(rb_file)
  @exports << { :rb_file => rb_file, :header => File.join(@ext_dir, File.basename(rb_file).sub(/\.rb$/, '.h')) }
end
find_library(lib, func, *paths) click to toggle source
# File lib/ffi-compiler/compile_task.rb, line 62
def find_library(lib, func, *paths)
  try_library(lib, function: func, paths: @library_paths) || try_library(libname, function: func, paths: paths)
end
have_func?(func) click to toggle source
# File lib/ffi-compiler/compile_task.rb, line 37
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
have_header?(header, *paths) click to toggle source
# File lib/ffi-compiler/compile_task.rb, line 50
def have_header?(header, *paths)
  try_header(header, @include_paths) || try_header(header, paths)
end
have_library(lib, func = nil, headers = nil, &b) click to toggle source
# File lib/ffi-compiler/compile_task.rb, line 58
def have_library(lib, func = nil, headers = nil, &b)
  try_library(lib, function: func, headers: headers, paths: @library_paths)
end
have_library?(libname, *paths) click to toggle source
# File lib/ffi-compiler/compile_task.rb, line 54
def have_library?(libname, *paths)
  try_library(libname, paths: @library_paths) || try_library(libname, paths: paths)
end

Private Instance Methods

cc() click to toggle source
# File lib/ffi-compiler/compile_task.rb, line 210
def cc
  @cc ||= (ENV['CC'] || RbConfig::CONFIG['CC'] || 'cc')
end
cxx() click to toggle source
# File lib/ffi-compiler/compile_task.rb, line 214
def cxx
  @cxx ||= (ENV['CXX'] || RbConfig::CONFIG['CXX'] || 'c++')
end
define_task!() click to toggle source
# File lib/ffi-compiler/compile_task.rb, line 71
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 = so_flags.join(' ')

  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 = (@cflags + pic_flags + iflags + defines).join(' ')
  cxxflags = (@cxxflags + @cflags + pic_flags + iflags + defines).join(' ')
  ld_flags = (@library_paths.map { |path| "-L#{path}" } + @ldflags).join(' ')
  libs = (@libraries.map { |l| "-l#{l}" } + @libs).join(' ')

  src_files = FileList["#{@ext_dir}/**/*.{c,cpp}"]
  obj_files = src_files.ext('.o').map { |f| File.join(out_dir, f.sub(/^#{@ext_dir}\//, '')) }
  ld = src_files.detect { |f| f =~ /\.cpp$/ } ? cxx : cc

  src_files.each do |src|
    obj_file = File.join(out_dir, src.sub(/\.(c|cpp)$/, '.o').sub(/^#{@ext_dir}\//, ''))
    if src =~ /\.c$/
      file obj_file => [ src, File.dirname(obj_file) ] do |t|
        sh "#{cc} #{cflags} -o #{t.name} -c #{t.prerequisites[0]}"
      end

    else
      file obj_file => [ src, File.dirname(obj_file) ] do |t|
        sh "#{cxx} #{cxxflags} -o #{t.name} -c #{t.prerequisites[0]}"
      end
    end

    CLEAN.include(obj_file)
  end

  # 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"
  file lib_name => obj_files do |t|
    sh "#{ld} #{so_flags} -o #{t.name} #{t.prerequisites.join(' ')} #{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')} #{File.join(File.dirname(__FILE__), 'exporter.rb')} #{t.prerequisites[0]} #{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
try_compile(src, *opts) click to toggle source
# File lib/ffi-compiler/compile_task.rb, line 196
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
    begin
      return system "#{cc} #{opts.join(' ')} -o #{File.join(dir, 'ffi-test')} #{path} > /dev/null 2>&1"
    rescue
      return false
    end
  end
end
try_header(header, paths) click to toggle source
# File lib/ffi-compiler/compile_task.rb, line 152
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
try_library(libname, options = {}) click to toggle source
# File lib/ffi-compiler/compile_task.rb, line 174
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