lib/rbplusplus/extension.rb in rbplusplus-0.8 vs lib/rbplusplus/extension.rb in rbplusplus-0.9
- old
+ new
@@ -1,43 +1,40 @@
+require 'optparse'
+
module RbPlusPlus
- # This is the starting class for Rb++ wrapping. All Rb++ projects start with this
- # class:
+ # This is the starting class for Rb++ wrapping. All Rb++ projects start as such:
#
# Extension.new "extension_name" do |e|
# ...
# end
#
- # "extension_name" is what the resulting Ruby library will be named, aka in your code
- # you will have
+ # where "extension_name" is what the resulting Ruby library will be named.
#
- # require "extension_name"
+ # For most cases, the block format will work. If you need more detailed control
+ # over the code generation process, you can use an immediate mode:
#
- # It is recommended that you use the block format of this class's initializer.
- # If you want more fine-grained control of the whole process, don't use
- # the block format. Instead you should do the following:
- #
# e = Extension.new "extension_name"
# ...
#
# The following calls are required in both formats:
#
- # #sources - The directory / array / name of C++ header files to parse.
+ # e.sources - The directory / array / name of C++ header files to parse.
#
# In the non-block format, the following calls are required:
#
- # #working_dir - Specify the directory where the code will be generated. This needs
+ # e.working_dir - Specify the directory where the code will be generated. This needs
# to be a full path.
#
- # In the non-block format, you need to manually fire the different steps of the
- # code generation process, and in this order:
+ # In immediate mode, you must to manually fire the different steps of the
+ # code generation process in this order:
#
- # #build - Fires the code generation process
+ # e.build - Fires the code generation process
#
- # #write - Writes out the generated code into files
+ # e.write - Writes out the generated code into files
#
- # #compile - Compiles the generated code into a Ruby extension.
+ # e.compile - Compiles the generated code into a Ruby extension.
#
class Extension
# Where will the generated code be put
attr_accessor :working_dir
@@ -50,11 +47,13 @@
#
# See #sources for a list of the possible options
attr_accessor :options
# Create a new Ruby extension with a given name. This name will be
- # the module built into the extension.
+ # the actual name of the extension, e.g. you'll have name.so and you will
+ # call require 'name' when using your new extension.
+ #
# This constructor can be standalone or take a block.
def initialize(name, &block)
@name = name
@modules = []
@writer_mode = :multiple
@@ -69,11 +68,15 @@
:includes => []
}
@node = nil
- if block
+ parse_command_line
+
+ if requesting_console?
+ block.call(self) if block
+ elsif block
build_working_dir(&block)
block.call(self)
build
write
compile
@@ -91,13 +94,28 @@
# * <tt>:libraries</tt> - Path(s) to be added as -l flags
# * <tt>:cxxflags</tt> - Flag(s) to be added to command line for parsing / compiling
# * <tt>:ldflags</tt> - Flag(s) to be added to command line for linking
# * <tt>:includes</tt> - Header file(s) to include at the beginning of each .rb.cpp file generated.
# * <tt>:include_source_files</tt> - C++ source files that need to be compiled into the extension but not wrapped.
+ # * <tt>:include_source_dir</tt> - A combination option for reducing duplication, this option will
+ # query the given directory for source files, adding all to <tt>:include_source_files</tt> and
+ # adding all h/hpp files to <tt>:includes</tt>
+ #
def sources(dirs, options = {})
parser_options = {}
+ if (code_dir = options.delete(:include_source_dir))
+ options[:include_source_files] ||= []
+ options[:includes] ||= []
+ Dir["#{code_dir}/*"].each do |f|
+ next if File.directory?(f)
+
+ options[:include_source_files] << f
+ options[:includes] << f if File.extname(f) =~ /hpp/i || File.extname(f) =~ /h/i
+ end
+ end
+
if (paths = options.delete(:include_paths))
@options[:include_paths] << paths
parser_options[:includes] = paths
end
@@ -129,20 +147,27 @@
else
@options[:includes] += [*includes]
end
end
+ @options[:includes] += [*dirs]
+
@sources = Dir.glob dirs
+ Logger.info "Parsing #{@sources.inspect}"
@parser = RbGCCXML.parse(dirs, parser_options)
+
+ if requesting_console?
+ start_console
+ end
end
# Set a namespace to be the main namespace used for this extension.
# Specifing a namespace on the Extension itself will mark functions,
# class, enums, etc to be globally available to Ruby (aka not in it's own
# module)
#
- # For now, to get access to the underlying RbGCCXML query system, save the
+ # To get access to the underlying RbGCCXML query system, save the
# return value of this method:
#
# node = namespace "lib::to_wrap"
#
def namespace(name)
@@ -156,33 +181,39 @@
m = RbModule.new(name, @parser, &block)
@modules << m
m
end
- # How should we write out the source code? This can be one of two modes:
+ # Specify the mode with which to write out code files. This can be one of two modes:
+ #
# * <tt>:multiple</tt> (default) - Each class and module gets it's own set of hpp/cpp files
# * <tt>:single</tt> - Everything gets written to a single file
+ #
def writer_mode(mode)
raise "Unknown writer mode #{mode}" unless [:multiple, :single].include?(mode)
@writer_mode = mode
end
# Start the code generation process.
def build
raise ConfigurationError.new("Must specify working directory") unless @working_dir
raise ConfigurationError.new("Must specify which sources to wrap") unless @parser
- @builder = Builders::ExtensionBuilder.new(@name, @node || @parser)
- Builders::Base.additional_includes = @options[:includes]
- Builders::Base.sources = @sources
- @builder.modules = @modules
+ Logger.info "Beginning code generation"
+
+ @builder = Builders::ExtensionNode.new(@name, @node || @parser, @modules)
+ @builder.add_includes @options[:includes]
@builder.build
+ @builder.sort
+
+ Logger.info "Code generation complete"
end
# Write out the generated code into files.
# #build must be called before this step or nothing will be written out
def write
+ Logger.info "Writing code to files"
prepare_working_dir
process_other_source_files
# Create the code
writer_class = @writer_mode == :multiple ? Writers::MultipleFilesWriter : Writers::SingleFileWriter
@@ -190,30 +221,78 @@
# Create the extconf.rb
extconf = Writers::ExtensionWriter.new(@builder, @working_dir)
extconf.options = @options
extconf.write
+ Logger.info "Files written"
end
# Compile the extension.
- # This will create an rbpp_compile.log file in @working_dir. View this
+ # This will create an rbpp_compile.log file in +working_dir+. View this
# file to see the full compilation process including any compiler
# errors / warnings.
def compile
+ Logger.info "Compiling. See rbpp_compile.log for details."
+ require 'rbconfig'
+ ruby = File.join(Config::CONFIG["bindir"], Config::CONFIG["RUBY_INSTALL_NAME"])
FileUtils.cd @working_dir do
- system("ruby extconf.rb > rbpp_compile.log 2>&1")
+ system("#{ruby} extconf.rb > rbpp_compile.log 2>&1")
+ system("rm -f *.so")
system("make >> rbpp_compile.log 2>&1")
end
+ Logger.info "Compilation complete."
end
protected
+ # Read any command line arguments and process them
+ def parse_command_line
+ OptionParser.new do |opts|
+ opts.banner = "Usage: ruby #{$0} [options]"
+
+ opts.on_head("-h", "--help", "Show this help message") do
+ puts opts
+ exit
+ end
+
+ opts.on("-v", "--verbose", "Show all progress messages (INFO, DEBUG, WARNING, ERROR)") do
+ Logger.verbose = true
+ end
+
+ opts.on("-q", "--quiet", "Only show WARNING and ERROR messages") do
+ Logger.quiet = true
+ end
+
+ opts.on("--console", "Open up a console to query the source via rbgccxml") do
+ @requesting_console = true
+ end
+
+ opts.on("--clean", "Force a complete clean and rebuild of this extension") do
+ @force_rebuild = true
+ end
+
+ end.parse!
+ end
+
+ # Check ARGV to see if someone asked for "console"
+ def requesting_console?
+ @requesting_console
+ end
+
+ # Start up a new IRB console session giving the user access
+ # to the RbGCCXML parser instance to do real-time querying
+ # of the code they're trying to wrap
+ def start_console
+ puts "IRB Session starting. @parser is now available to you for querying your code. The extension object is available as 'self'"
+ IRB.start_session(binding)
+ end
+
# If the working dir doesn't exist, make it
# and if it does exist, clean it out
def prepare_working_dir
FileUtils.mkdir_p @working_dir unless File.directory?(@working_dir)
- FileUtils.rm_rf Dir["#{@working_dir}/*"]
+ FileUtils.rm_rf Dir["#{@working_dir}/*"] if @force_rebuild #ARGV.include?("clean")
end
# Make sure that any files or globs of files in :include_source_files are copied into the working
# directory before compilation
def process_other_source_files
@@ -223,10 +302,43 @@
end
end
# Cool little eval / binding hack, from need.rb
def build_working_dir(&block)
+ file_name =
+ if block.respond_to?(:source_location)
+ block.source_location[0]
+ else
+ eval("__FILE__", block.binding)
+ end
+
@working_dir = File.expand_path(
- File.join(File.dirname(eval("__FILE__", block.binding)), "generated"))
+ File.join(File.dirname(file_name), "generated"))
end
end
end
+
+require 'irb'
+
+module IRB # :nodoc:
+ def self.start_session(binding)
+ unless @__initialized
+ args = ARGV
+ ARGV.replace(ARGV.dup)
+ IRB.setup(nil)
+ ARGV.replace(args)
+ @__initialized = true
+ end
+
+ workspace = WorkSpace.new(binding)
+
+ irb = Irb.new(workspace)
+
+ @CONF[:IRB_RC].call(irb.context) if @CONF[:IRB_RC]
+ @CONF[:MAIN_CONTEXT] = irb.context
+
+ catch(:IRB_EXIT) do
+ irb.eval_input
+ end
+ end
+end
+