#!/usr/bin/env ruby LICENSE = < This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ GPLv3 require "optparse" require "ostruct" # Static templates: TPL_FILENAME = 'CLibraryLoader#{options.name}.hpp' TPL_INCLUDE = '#include <#{include_file}>' TPL_STRUCT_COMMENT = ' // generated by dlib_generator.rb for #{header_file}' TPL_STRUCT_POINTER = ' #{@return} (*#{@name})(#{@argument_list});' TPL_LOAD_COMMENT = ' // generated by dlib_generator.rb for #{header_file}' TPL_LOAD_SYMBOL = ' m_symbols->#{@name} = (#{@return} (*)(#{@argument_list})) LoadProcedure(\"#{@name}\");' TPL_DEFINE_COMMENT = '// generated by dlib_generator.rb for #{header_file}' TPL_DEFINE = '#define #{@name}(#{@argument_num_list}) (DLIB_#{@@library_name.upcase}.GetSymbols()->#{@name})(#{@argument_num_list})' # Used to parse configuration file. The fileformat is very simple as you can see # it just takes the keys as arguments class GeneratorConfigParser def initialize(filename) if not File.exists? filename # or not a file puts "ERROR: File not found: #{filename}" exit end @config = {} IO.readlines(filename).each do |line| line.strip.match /^([#])?([^ ]+)( = )?(.*)?$/ next if $1 == '#' or $2 == '#' if $2 and not $2.empty? key = $2 value = $4.gsub(/^\"(.*)\"$/, "\\1") if $4 @config[key] = value end end end # end initialize def createArguments() arguments = [] @config.each_pair do |key, value| arguments << "--"+key arguments << value if value and not value.empty? end return arguments end end # Used to parse command-line parameters class GeneratorOptionParser def self.parse(args) # setup default values for options options = OpenStruct.new # generator options options.name = nil options.template = "dlib_generator.tpl" options.config = nil options.write = nil options.filename = nil # "virtual" is created on-the-fly from template options.define = false options.include = nil # header options options.header_file = nil options.header_path = nil options.file_pattern = "\\.h$" options.prototype_pattern = nil # binary options options.library_file = nil options.symbol_prefix = "" # miscellaneous options.verbose = false # create option parser opts = OptionParser.new do |opts| opts.banner = "Usage: #{$0} [OPTIONS]\n" + "Parse shared library binary and headers to generate code for a dynamic loader." opts.separator "" opts.separator "You need at least specify a name, a header file (or directory) and a prototype" opts.separator "pattern. The binary options are optional, but can help to improve the result." opts.separator "" # generator options opts.separator "Generator Options:" opts.on("-n", "--name NAME", "The name used to identify the library") do |name| options.name = name end opts.on("-t", "--template FILE", "Use FILE as generator template ", "(Default: #{options.template})") do |template| options.template = template end opts.on("-c", "--config FILE", "Use FILE for configuration, overwrites other options") do |config| options.config = config end opts.on("-w", "--write [FILE]", "Write generated code to FILE, in addition to stdout", "(Default file: #{TPL_FILENAME})", "Need to be specified after name!") do |write| options.write = true if not write or write.empty? if not options.name puts "ERROR: you need to specify name before write." exit end options.filename = eval('"'+TPL_FILENAME+'"') else options.filename = write end end opts.on("-d", "--define", "Create a C macro for each procedure") do options.define = true end opts.on("-i", "--include a.hpp,b.hpp", Array, "List include header files.") do |include| options.include = include end opts.separator "" # header options opts.separator "Library Header Options:" opts.on("-f", "--header_file FILE", "Use FILE or PATH for header declarations") do |header_file| options.header_file = header_file end opts.on("-p", "--header_path PATH") do |header_path| options.header_path = header_path end opts.on("-r", "--file_pattern PATTERN", "Parse only files in header PATH that matches", "PATTERN (Default: #{options.file_pattern})") do |file_pattern| options.file_pattern = file_pattern end opts.on("-R", "--prototype_pattern PATTERN", "Use PATTERN to parse procedures, assumes three", "groups: Return, procedure name and arguments") do |prototype_pattern| options.prototype_pattern = prototype_pattern end opts.separator "" # binary options opts.separator "Library Binary Options:" opts.on("-l", "--library FILE", "Shared library FILE parsed with nm for symbols") do |library_file| options.library_file = library_file end opts.on("-s", "--symbol_prefix PREFIX", "Use symbol PREFIX to extract procedure names", "from shared library") do |symbol_prefix| options.symbol_prefix = symbol_prefix end opts.separator "" # miscellaneous options opts.separator "Miscellaneous:" opts.on("-v", "--verbose", "Activates console messages") do |verbose| options.verbose = true end opts.on("-h", "--help", "Show this message") do puts opts exit end opts.separator "" end # end OptionParser # save args for later merge with configuration args arg_copy = args.dup # parse arguments with created option parser opts.parse!(args) # if configuration file was defined, parse it as arguments again if options.config config = GeneratorConfigParser.new(options.config) opts.parse!(config.createArguments() + arg_copy) end # check for required options: usage = false if not options.name puts "ERROR: You need to specify a name." usage = true end if not options.header_file and not options.header_path puts "ERROR: You need to specify a header file or directory." usage = true end if not options.prototype_pattern puts "ERROR: You need to specify a prototype pattern." usage = true end if usage puts puts opts exit end # return resulting option structure return options end # end static parse method end # end GeneratorOptionParser # Procedure function declaration/prototype class class Procedure @@library_name = "" def initialize(name, return_type, arguments) @name = name @return = return_type @arguments = arguments @argument_list = arguments.join(', ') @arguments_num = [] @arguments.each_index do |num| @arguments_num << "v#{num}" end @argument_num_list = @arguments_num.join(',') end def createPointer() return eval('"'+TPL_STRUCT_POINTER+'"') end def createLoadSymbol() return eval('"'+TPL_LOAD_SYMBOL+'"') end def createDefine() return eval('"'+TPL_DEFINE+'"') end def self.setLibraryName(name) @@library_name = name end end # end procedure # Performs the actual parsing of library headers and binary class LibraryParser @@verbose = false # print some additional informations def initialize() @headers_content = {} @library_symbols = [] @procedures = {} end # read the contents of a single header file def readHeaderFile(header_file) return if @headers_content.has_key? header_file or not File.exists? header_file puts "Read header file #{header_file}" if @@verbose @headers_content[header_file] = _readFileContents(header_file) end # lists all files in the header path and read every single header # that matches prefix (if any) def readHeaderPath(header_path, file_pattern = ".*") puts "Read header path #{header_path}" if @@verbose Dir.new(header_path).each do |header_file| next if not header_file.match(Regexp.new(file_pattern)) readHeaderFile(header_path + "/" + header_file) end end # read symbols of binary header with nm into @library_symbols array # if array is not empty during parsing process, it will be checked # against for each symbol name. def readSymbols(library_file, symbol_prefix) puts "Read symbols from shared library binary #{library_file}" if @@verbose nm = `nm -D --defined-only #{library_file}` nm.scan(/ T (#{Regexp.escape(symbol_prefix)}.*)$/).each do |symbol| puts "Found symbol name: #{symbol[0]}" if @@verbose @library_symbols << symbol[0] end puts "(found #{@library_symbols.length} symbols)" if @@verbose end # parse procedure declarations/prototypes from headers with the specified # regular expression, the pattern assumes three match groups: return type, # name and arguments. Found procedures are stored in Procedure instances # within the @procedures hash. def parseProcedures(prototype_pattern) @headers_content.each_pair do |header_file, header_contents| puts "\nParse header file #{header_file} for procedures (#{header_contents.length})" if @@verbose next if @procedures.has_key? header_file @procedures[header_file] = [] header_contents.scan(Regexp.new(prototype_pattern, Regexp::MULTILINE)) do |match| return_type = match[0].strip name = match[1].strip arguments = match[2].strip.split(',').map{ |argument| argument.strip } # if library symbols is not empty, ignore every procedure not within if not @library_symbols.empty? if @library_symbols.include?(name) @library_symbols.delete(name) else next end end # create new Procedure object and append to procedures hash @procedures[header_file] << Procedure.new(name, return_type, arguments) next if not @@verbose puts "\t Filename : " + header_file puts "\t Name : " + name.inspect puts "\t Return : " + return_type.inspect puts "\tArguments : " + arguments.inspect puts end end return @procedures end # end parseProcedures def getRemainingSymbols() return @library_symbols.join(", ") end def getProcedures() @procedures end # a private method for reading a (text) file def _readFileContents(filename) if not File.exists? filename # or not a file puts "ERROR: File not found: #{filename}" exit end IO.readlines(filename).join end def self.setVerboseMode(verbose) @@verbose = verbose end end # end LibraryParser # Populates template variables and blocks as instance attributes, reads # the template file and creates the resulting class. class LibraryTemplate @@verbose = false def initialize(name, filename, template, procedures) @name = name @filename = filename @template = _readTemplate(template) @procedures = procedures if @@verbose print "LibraryTemplate created," puts " name=#{name} filename=#{filename} template=#{template}" puts end # create blocks @block_include = "" @block_define = "" _createBlockSymbolsStruct() _createBlockLoadSymbols() end def createBlockInclude(include) return if not include or include.empty? include.each do |include_file| @block_include += eval('"'+TPL_INCLUDE+'\n"') end end def _createBlockSymbolsStruct() @block_symbols_struct = "" @procedures.each_pair do |header_file, procedures| next if procedures.empty? @block_symbols_struct += eval('"'+TPL_STRUCT_COMMENT+'\n"') # iterate over procedures: procedures.each do |procedure| @block_symbols_struct += procedure.createPointer() + "\n" end @block_symbols_struct += "\n" end end def _createBlockLoadSymbols() @block_load_symbols = "" @procedures.each_pair do |header_file, procedures| next if procedures.empty? @block_load_symbols += eval('"'+TPL_LOAD_COMMENT+'\n"') # iterate over procedures: procedures.each do |procedure| @block_load_symbols += procedure.createLoadSymbol() + "\n" end @block_load_symbols += "\n" end end def createBlockDefine() @block_define = "" @procedures.each_pair do |header_file, procedures| next if procedures.empty? @block_define += eval('"'+TPL_DEFINE_COMMENT+'\n"') # iterate over procedures: procedures.each do |procedure| @block_define += procedure.createDefine() + "\n" end @block_define += "\n" end end def evalTemplate() return eval("<