From d01bb62eb41ddb1f813aaa637a23ff8f577b5b81 Mon Sep 17 00:00:00 2001 From: Tilman Sauerbeck Date: Thu, 5 Apr 2007 21:59:23 +0200 Subject: [PATCH] Use a configure task and an extension task to build ruby-edje. Note that I had to edit extensiontask.rb to include sitearchdir so that the ruby-evas headers would be picked up. --- README | 4 - Rakefile | 40 +++--- library.rb | 33 ----- rake/configuretask.rb | 296 ++++++++++++++++++++++++++++++++++++++++++ rake/extensiontask.rb | 189 +++++++++++++++++++++++++++ 5 files changed, 510 insertions(+), 52 deletions(-) delete mode 100644 library.rb create mode 100644 rake/configuretask.rb create mode 100644 rake/extensiontask.rb diff --git a/README b/README index 010b1ac..5678654 100644 --- a/README +++ b/README @@ -14,10 +14,6 @@ ruby-edje is available under the GNU LGPL. == Installation Run "rake install" to install ruby-edje. -If you didn't install Edje to /usr/local, execute the following line -instead: - -EDJE_PREFIX=/prefix/edje/is/installed/in rake install == Documentation diff --git a/Rakefile b/Rakefile index 9048ee3..a8313fb 100644 --- a/Rakefile +++ b/Rakefile @@ -1,4 +1,3 @@ -require "library" require "rake/clean" require "rake/testtask" require "rake/rdoctask" @@ -6,34 +5,45 @@ require "rake/packagetask" require "rake/contrib/compositepublisher" require "rake/contrib/sshpublisher" +require "rake/configuretask" +require "rake/extensiontask" + PKG_NAME = "ruby-edje" -CLOBBER.include("src/*.{s,}o") +ext_objs = [:rb_edje_main, :rb_edje, :rb_part] + +task :default => [:ext] -ext_obj = Dir["src/*.c"].map { |f| f.sub(/\.[^.]+$/, ".o") } -ext_lib = Library.new("edje", ENV["EDJE_PREFIX"] || "/usr/local", "src") +config = Rake::ConfigureTask.new do |t| + t.tests << Rake::ConfigureTask:: + PkgConfigTest.new("edje", :is_critical => true) +end -task :default => [ext_lib.so] +task :ext => [:pre_ext] -file ext_lib.so => ext_lib.objects do |t| - sh "cc #{ext_lib.ldflags} -shared -Wl " + - " #{t.prerequisites.join(" ")} -o #{t.name}" +ext = Rake::ExtensionTask.new :ext => ext_objs do |t| + t.dir = "src" + t.lib_name = "#{t.dir}/edje.so" end -ext_lib.objects.each do |object| - file object => object.sub(/\.[^.]+$/, ".c") do |t| - sh "cc #{ext_lib.cflags} #{t.prerequisites.first} " + - "-c -o #{t.name}" - end +task :pre_ext => [:configure] do + ext.link_libs << config.edje.libs + + cflags = [ + ext.env[:cflags], + config.edje.cflags + ] + + ext.env.update(:cflags => cflags) end -task :install => [ext_lib.so] do |t| +task :install => [:ext] 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(ext_lib.so, ddir, :mode => 0755) + FileUtils::Verbose.install(ext.lib_name, ddir, :mode => 0755) end Rake::RDocTask.new do |t| diff --git a/library.rb b/library.rb deleted file mode 100644 index 21edae1..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 -- 2.30.2