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 +