From: Tilman Sauerbeck Date: Sun, 8 Apr 2007 11:54:32 +0000 (+0200) Subject: Use a configure task and extension tasks to build ruby-ecore. X-Git-Url: http://git.code-monkey.de/?p=ruby-ecore.git;a=commitdiff_plain;h=087974f38a80e4052d828562c01cdb44eacd3bf5 Use a configure task and extension tasks to build ruby-ecore. --- diff --git a/.gitignore b/.gitignore index 74cb124..2a556c0 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ doc pkg *.o *.so +.configure_state.yaml diff --git a/Rakefile b/Rakefile index c7252ba..a6a2850 100644 --- a/Rakefile +++ b/Rakefile @@ -1,4 +1,3 @@ -require "library" require "rake/clean" require "rake/testtask" require "rake/rdoctask" @@ -6,37 +5,75 @@ require "rake/packagetask" require "rake/contrib/compositepublisher" require "rake/contrib/sshpublisher" +require "rake/configuretask" +require "rake/extensiontask" + PKG_NAME = "ruby-ecore" -CLOBBER.include("src/*/*.{s,}o") +modules = [:ecore, :ecore_evas, :ecore_job, :ecore_x, :ecore_con] +module_objects = { + :ecore => [ + :rb_ecore, :rb_animator, :rb_event_handler, :rb_fd_handler, + :rb_idle_enterer, :rb_idler, :rb_timer + ], + :ecore_evas => [ + :rb_ecore_evas_main, :rb_ecore_evas, + :rb_buffer, :rb_fb, :rb_gl_x11, + :rb_software_x11, :rb_xrender_x11 + ], + :ecore_job => [:rb_ecore_job, :rb_job], + :ecore_x => [:rb_ecore_x, :rb_window, :rb_cursor], + :ecore_con => [:rb_ecore_con, :rb_server] +} + +module_tasks = modules.map { |mod| "ext_#{mod}".to_sym } + +task :default => module_tasks + +config = Rake::ConfigureTask.new do |t| + modules.each do |mod| + d = mod.to_s.sub(/ecore_/, "ecore-") + t.tests << Rake::ConfigureTask:: + PkgConfigTest.new(d, :is_critical => true) + end +end -ext_libs = %w{ecore ecore_evas ecore_job ecore_x ecore_con}.map do |lib| - Library.new(lib, ENV["ECORE_PREFIX"] || "/usr/local", "src/#{lib}") +module_tasks.each do |mt| + task mt => ["pre_#{mt}".to_sym] end -task :default => ext_libs.map { |l| l.so } +exts = {} + +modules.each_with_index do |mod, i| + name = module_tasks[i] -ext_libs.each do |lib| - file lib.so => lib.objects do |t| - sh "cc #{lib.ldflags} #{t.prerequisites.join(" ")} -o #{t.name}" + exts[mod] = Rake::ExtensionTask.new name => module_objects[mod] do |t| + t.dir = "src/#{mod}" + t.lib_name = "#{t.dir}/#{mod}.so" end - lib.objects.each do |object| - file object => object.sub(/\.[^.]+$/, ".c") do |t| - sh "cc #{lib.cflags} #{t.prerequisites.first} " + - "-c -o #{t.name}" - end + task "pre_#{name}".to_sym => [:configure] do + d = mod.to_s.sub(/ecore_/, "ecore-") + exts[mod].link_libs << config[d].libs + + cflags = [ + exts[mod].env[:cflags], + config[d].cflags + ] + + exts[mod].env.update(:cflags => cflags) end end -task :install => ext_libs.map { |l| l.so } do |t| +task :install => module_tasks do |t| destdir = "#{ENV["DESTDIR"]}" sitearchdir = ENV["RUBYARCHDIR"] || Config::CONFIG['sitearchdir'] ddir = destdir + sitearchdir FileUtils::Verbose.mkdir_p(ddir) unless File.directory?(ddir) - FileUtils::Verbose.install(t.prerequisites.to_a, ddir, - :mode => 0755) + + sos = exts.values.map { |e| e.lib_name } + FileUtils::Verbose.install(sos, ddir, :mode => 0755) ddir = destdir + sitearchdir + "/ecore" FileUtils::Verbose.mkdir_p(ddir) unless File.directory?(ddir) diff --git a/library.rb b/library.rb deleted file mode 100644 index 33a3132..0000000 --- a/library.rb +++ /dev/null @@ -1,33 +0,0 @@ -require "rbconfig" - -class Library - def initialize(name, prefix, dir, lib = name) - @name = name - @prefix = prefix - @dir = dir - @lib = lib - end - - def ldflags - s = "#{ENV["LDFLAGS"]} -L #{@prefix}/lib -L " + - Config::CONFIG["libdir"] + " " + - Config::CONFIG["LIBRUBYARG_SHARED"] + - " -shared -Wl" - s << " -l#{@lib}" unless @lib.nil? - s - end - - def cflags - "#{ENV["CFLAGS"]} -I #{@prefix}/include " + - "-I #{Config::CONFIG['archdir']} " + - "-I #{Config::CONFIG['sitearchdir']} -fPIC" - end - - def so - "#{@dir}/#{@name}.so" - end - - def objects - Dir["#{@dir}/*.c"].map { |f| f.sub(/\.[^.]+$/, ".o") } - end -end diff --git a/rake/configuretask.rb b/rake/configuretask.rb new file mode 100644 index 0000000..b673c84 --- /dev/null +++ b/rake/configuretask.rb @@ -0,0 +1,296 @@ +# +# Copyright (c) 2005, 2006 Tilman Sauerbeck (tilman at code-monkey de) +# +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: +# +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +require "rake/tasklib" +require "rake/clean" +require "yaml" +require "fileutils" + +module Rake + class ConfigureTask < TaskLib + CACHE_FILE = ".configure_state.yaml" + + attr_reader :tests + + def initialize # :yield: self + @tests = TestList.new(load_tests || []) + + yield self if block_given? + + define + end + + # returns the test with the specified name + def [](name) + @tests.find { |t| t.name == name } + end + + def method_missing(m) + self[m.to_s] + end + + private + def load_tests + r = YAML.load(File.read(CACHE_FILE)) rescue nil + + r.is_a?(TestList) ? r : nil + end + + def define + desc "Remove configure results" + task :clobber_configure do + FileUtils::Verbose.rm_f(CACHE_FILE) + end + + task :clobber => :clobber_configure + + desc "Configure this package" + task :configure => [CACHE_FILE] + + file CACHE_FILE do + @tests.each do |t| + t.on_checking.reverse_each { |b| b.call } + + if t.invoke + t.on_success.reverse_each { |b| b.call } + else + t.on_failure.reverse_each { |b| b.call } + end + end + + # store the test results in CACHE_FILE + File.open(CACHE_FILE, "w") { |f| YAML.dump(@tests, f) } + end + end + + class TestList < Array + def initialize(stored_tests) + @stored_tests = stored_tests + end + + def <<(arg) + assign_result(arg) + super + end + + def push(*args) + args.each { |a| assign_result(a) } + super + end + + def unshift(arg) + assign_result(arg) + super + end + + private + def assign_result(test) + st = @stored_tests.find { |st| st.name == test.name } + test.result = st.result unless st.nil? + end + end + + class Test + attr_reader :name, :on_checking, :on_success, :on_failure + attr_accessor :result + + def initialize(name, opts = {}) # :yield: self + @name = name + @opts = opts + + @result = nil + @on_checking = [] + @on_success = [] + @on_failure = [] + + if opts[:is_critical] + @on_failure << lambda { raise } + end + + yield self if block_given? + end + + def to_yaml_properties + ["@name", "@result"] + end + + def invoke + end + + protected + def can_exec_binary?(bin) + fork do + STDOUT.reopen("/dev/null") + STDERR.reopen("/dev/null") + + begin + exec(bin) + rescue SystemCallError + exit 255 + end + end + + Process.wait + + $?.exitstatus != 255 + end + end + + class FooConfigTest < Test + def initialize(name, opts = {}) + super + + @result = {} + @command = "#{name}-config" + + @on_checking << lambda do + print "checking for #{name}... " + STDOUT.flush + end + + @on_success << lambda { puts "yes (#{version})" } + @on_failure << lambda { puts "no" } + end + + def method_missing(m) + @result[m] + end + + def invoke + return false unless can_exec_command? + + begin + [:version, :cflags, :libs].each do |f| + @result[f] = lookup_flags(f) + end + rescue Exception + @result.clear + end + + !@result.empty? + end + + protected + def lookup_flags(f) + tmp = `#{@command} --#{f}`.strip + + raise unless $?.exitstatus.zero? + tmp + end + + private + def can_exec_command? + can_exec_binary?(@command) + end + end + + class PkgConfigTest < FooConfigTest + def initialize(name, opts = {}) + super + + @command = "pkg-config" + end + + protected + def lookup_flags(f) + f = :modversion if f == :version + + tmp = `#{@command} --silence-errors --#{f} #{@name}`. + strip.tr("\n", "/") + + raise unless $?.exitstatus.zero? + tmp + end + end + + class CompileTest < Test + TMP_FILE = ".compile_test" + + def CompileTest.cflags + @@cflags + end + + def CompileTest.cflags=(f) + @@cflags = f + end + + def initialize(name, code, opts = {}) + super(name, opts) + + @code = code + end + + def invoke + @result = false + + cc = ENV["CC"] || "cc" + flags = (ENV["CFLAGS"] || "").dup + flags << " -I" + Config::CONFIG["archdir"] + + unless @opts[:try_link] + flags << " -c" + end + + File.open(TMP_FILE + ".c", "w") do |f| + f << @code << "\n" + end + + `#{cc} #{flags} #{TMP_FILE}.c -o #{TMP_FILE}.o > /dev/null 2>&1` + @result = $?.exitstatus.zero? + ensure + FileUtils.rm_f("#{TMP_FILE}.c") + FileUtils.rm_f("#{TMP_FILE}.o") + end + end + + class HaveFuncTest < CompileTest + def initialize(name, includes = [], opts = {}) + super(name, assemble_code(name, includes), opts) + + @on_checking << lambda do + print "checking for #{name}... " + STDOUT.flush + end + + @on_success << lambda { puts "yes" } + @on_failure << lambda { puts "no" } + end + + private + def assemble_code(func, includes) + header = includes.inject("") do |a, h| + a << "#include <#{h}>\n" + end + + body =< :foo do |t| + # # all extension files under this directory + # t.dir = 'ext' + # # link libraries (libbar.so) + # t.link_libs << 'bar' + # end + # + # Author:: Steve Sloan (mailto:steve@finagle.org) + # Copyright:: Copyright (c) 2006 Steve Sloan + # License:: GPL + + class ExtensionTask < Rake::TaskLib + # The name of the extension + attr_accessor :name + + # The filename of the extension library file (e.g. 'extension.so') + attr_accessor :lib_name + + # Object files to build and link into the extension. + attr_accessor :objs + + # The directory where the extension files (source, output, and + # intermediate) are stored. + attr_accessor :dir + + # Environment configuration -- i.e. CONFIG from rbconfig, with a few other + # settings, and converted to lowercase-symbols. + attr_accessor :env + + # Additional link libraries + attr_accessor :link_libs + + # Same arguments as Rake::define_task + def initialize( args, &blk ) + @env = @@DefaultEnv.dup + @name, @objs = resolve_args(args) + set_defaults + yield self if block_given? + define_tasks + end + + # Generate default values. This is called from initialize _before_ the + # yield block. + # + # Defaults: + # - lib_name: name.so + # - objs: name.o (<- name.{c,cxx,cpp,cc}) + # - dir: . + # - link_libs: + def set_defaults + @lib_name ||= name.to_sym + @objs ||= [name.to_sym] + @dir ||= '.' + @link_libs ||= [] + end + + # Defines the library task. + def define_tasks + output_objs = @objs.collect { |obj| filepath obj, :objext } + output_lib = filepath lib_name, :dlext + + task name => output_lib + + file output_lib => output_objs do |t| + sh_cmd :ldshared, :dldflags, :ldflags, + {'-L' => :libdirs}, '-o', output_lib, + output_objs.join(' '), + link_libs.join(' '), + :libs, :dldlibs, :librubyarg_shared + end + + CLEAN.include output_objs + CLOBBER.include output_lib + define_rules + end + + # Defines C and C++ source-to-object rules, using the source extensions from env. + def define_rules + for ext in env[:c_exts] + Rake::Task.create_rule '.'+env[:objext] => '.'+ext do |r| + sh_cmd :cc, :cflags, :cppflags, {'-D' => :defines}, {'-I' => :includedirs}, {'-I' => :topdir}, {'-I' => :sitearchdir}, + '-c', '-o', r.name, r.sources + end + end + + for ext in env[:cpp_exts] + Rake::Task.create_rule '.'+env[:objext] => '.'+ext do |r| + sh_cmd :cxx, :cxxflags, :cppflags, {'-D' => :defines}, {'-I' => :includedirs}, {'-I' => :topdir}, {'-I' => :sitearchdir}, + '-o', r.name, '-c', r.sources + end + end + end + + class << self + # The default environment for all extensions. + @@DefaultEnv = {} + def env + @@DefaultEnv + end + def env=(e) + @@DefaultEnv = e + end + + Config::CONFIG.merge(ENV).each { |k, v| @@DefaultEnv[k.downcase.to_sym] = v } + @@DefaultEnv = { + :cxx => 'c++', + :cxxflags => '', + :c_exts => ['c'], + :cpp_exts => ['cc', 'cxx', 'cpp'], + :includedirs => [], + :libdirs => [], + }.update(@@DefaultEnv) + end + + protected + + # Handles convenience filenames: + # * f (String) => f + # * f (Symbol) => dir/f.ext + def filepath( f, ext ) + ext = env[ext] if Symbol === ext + Symbol === f ? File.join( dir, "#{f}.#{ext}" ) : f + end + + # Convenience function for cnstructing command lines for build tools. + def optify( *opts ) + return optify(*opts.first) if opts.size == 1 and opts.first.kind_of? Array + opts.collect do |opt| + case opt + when String then opt + when Symbol then optify env[opt] + when Hash + opt.collect do |k, v| + v = env[v] if v.kind_of? Symbol + if v.kind_of? Array + optify v.collect { |w| k.to_s + w.to_s } + elsif v + k.to_s + v.to_s + end + end + else + opt.to_s + end + end.join(' ').squeeze(' ') + end + + def sh_cmd( cmd, *opts ) + sh optify( cmd, *opts ) + end + + # For some reason, Rake::TaskManager.resolve_args can't be found, so snarf it. + def resolve_args(args) + case args + when Hash + fail "Too Many Task Names: #{args.keys.join(' ')}" if args.size > 1 + fail "No Task Name Given" if args.size < 1 + task_name = args.keys[0] + deps = args[task_name] + deps = [deps] if (String===deps) || (Regexp===deps) || (Proc===deps) + else + task_name = args + deps = [] + end + [task_name, deps] + end + + end + +end