class RbPlusPlus::Extension
This is the starting class for Rb++ wrapping. All Rb++ projects start as such:
Extension.new "extension_name" do |e| ... end
where “extension_name” is what the resulting Ruby library will be named.
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:
e = Extension.new "extension_name" ...
The following calls are required in both formats:
e.sources - The directory / array / name of C++ header files to parse.
In the non-block format, the following calls are required:
e.working_dir - Specify the directory where the code will be generated. This needs to be a full path.
In immediate mode, you must to manually fire the different steps of the code generation process in this order:
e.build - Fires the code generation process e.write - Writes out the generated code into files e.compile - Compiles the generated code into a Ruby extension.
Attributes
The list of modules to create
Various options given by the user to help with parsing, linking, compiling, etc.
See sources
for a list of the possible options
Where will the generated code be put
Public Class Methods
Create a new Ruby extension with a given name. This name will be 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.
# File lib/rbplusplus/extension.rb, line 56 def initialize(name, &block) @name = name @modules = [] @writer_mode = :multiple @requesting_console = false @force_rebuild = false @options = { :include_paths => [], :library_paths => [], :libraries => [], :cxxflags => [], :ldflags => [], :include_source_files => [], :includes => [] } @node = nil parse_command_line if requesting_console? block.call(self) if block start_console elsif block build_working_dir(&block) block.call(self) build write compile end end
Public Instance Methods
Start the code generation process.
# File lib/rbplusplus/extension.rb, line 208 def build raise ConfigurationError.new("Must specify working directory") unless @working_dir raise ConfigurationError.new("Must specify which sources to wrap") unless @parser 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
Compile the extension. 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.
# File lib/rbplusplus/extension.rb, line 244 def compile Logger.info "Compiling. See rbpp_compile.log for details." require 'rbconfig' ruby = File.join(RbConfig::CONFIG["bindir"], RbConfig::CONFIG["RUBY_INSTALL_NAME"]) FileUtils.cd @working_dir do 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
Mark that this extension needs to create a Ruby module of a give name. Like Extension.new
, this can be used with or without a block.
# File lib/rbplusplus/extension.rb, line 191 def module(name, &block) m = RbModule.new(name, @parser, &block) @modules << m m 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)
To get access to the underlying RbGCCXML
query system, save the return value of this method:
node = namespace "lib::to_wrap"
# File lib/rbplusplus/extension.rb, line 184 def namespace(name) @node = @parser.namespaces(name) end
Define where we can find the header files to parse Can give an array of directories, a glob, or just a string. All file names should be full paths, not relative.
Options can be any or all of the following:
-
:include_paths
- Path(s) to be added as -I flags -
:library_paths
- Path(s) to be added as -L flags -
:libraries
- Path(s) to be added as -l flags -
:cxxflags
- Flag(s) to be added to command line for parsing / compiling -
:ldflags
- Flag(s) to be added to command line for linking -
:includes
- Header file(s) to include at the beginning of each .rb.cpp file generated. -
:include_source_files
- C++ source files that need to be compiled into the extension but not wrapped. -
:include_source_dir
- A combination option for reducing duplication, this option will query the given directory for source files, adding all to:include_source_files
and adding all h/hpp files to:includes
# File lib/rbplusplus/extension.rb, line 106 def sources(dirs, options = {}) parser_options = { :includes => [], :cxxflags => [ # Force castxml into C++ mode "-x c++", # Allow things like `<::` "-fpermissive" ] } 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 end end if (paths = options.delete(:include_paths)) @options[:include_paths] << paths parser_options[:includes] << paths end if (lib_paths = options.delete(:library_paths)) @options[:library_paths] << lib_paths end if (libs = options.delete(:libraries)) @options[:libraries] << libs end if (flags = options.delete(:cxxflags)) @options[:cxxflags] << flags parser_options[:cxxflags] << flags end if (flags = options.delete(:ldflags)) @options[:ldflags] << flags end if (files = options.delete(:include_source_files)) @options[:include_source_files] << files options[:includes] ||= [] [files].flatten.each do |f| options[:includes] << f if File.extname(f) =~ /hpp/i || File.extname(f) =~ /h/i end end if (flags = options.delete(:includes)) includes = Dir.glob(flags) if(includes.length == 0) puts "Warning: There were no matches for includes #{flags.inspect}" else @options[:includes] += [*includes] end end @options[:includes] += [*dirs] @sources = Dir.glob dirs Logger.info "Parsing #{@sources.inspect}" @parser = RbGCCXML.parse(dirs, parser_options) end
Write out the generated code into files. build
must be called before this step or nothing will be written out
# File lib/rbplusplus/extension.rb, line 224 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 writer_class.new(@builder, @working_dir).write # Create the extconf.rb extconf = Writers::ExtensionWriter.new(@builder, @working_dir) extconf.options = @options extconf.write Logger.info "Files written" end
Specify the mode with which to write out code files. This can be one of two modes:
-
:multiple
(default) - Each class and module gets it's own set of hpp/cpp files -
:single
- Everything gets written to a single file
# File lib/rbplusplus/extension.rb, line 202 def writer_mode(mode) raise "Unknown writer mode #{mode}" unless [:multiple, :single].include?(mode) @writer_mode = mode end
Protected Instance Methods
Cool little eval / binding hack, from need.rb
# File lib/rbplusplus/extension.rb, line 317 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(file_name), "generated")) end
Read any command line arguments and process them
# File lib/rbplusplus/extension.rb, line 259 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
If the working dir doesn't exist, make it and if it does exist, clean it out
# File lib/rbplusplus/extension.rb, line 302 def prepare_working_dir FileUtils.mkdir_p @working_dir unless File.directory?(@working_dir) FileUtils.rm_rf Dir["#{@working_dir}/*"] if @force_rebuild end
Make sure that any files or globs of files in :include_source_files are copied into the working directory before compilation
# File lib/rbplusplus/extension.rb, line 309 def process_other_source_files files = @options[:include_source_files].flatten files.each do |f| FileUtils.cp Dir[f], @working_dir end end
Check ARGV to see if someone asked for “console”
# File lib/rbplusplus/extension.rb, line 288 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
# File lib/rbplusplus/extension.rb, line 295 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