Mercurial > projects > dstep
view scripts/dstepgen.rb @ 23:420969b22201
Removed an import to a private library. Added support for building as a dynamic library and framework
author | Jacob Carlborg <doob@me.com> |
---|---|
date | Wed, 10 Feb 2010 17:29:12 +0100 |
parents | 19885b43130e |
children | b9de51448c6b |
line wrap: on
line source
#!/usr/bin/env ruby # Copyright (c) 2008-2009, Jacob Carlborg. All rights reserved. # Copyright (c) 2006-2007, Apple Inc. All rights reserved. # Copyright (c) 2005-2006 FUJIMOTO Hisakuni # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # 3. Neither the name of Apple Inc. ("Apple") nor the names of # its contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING # IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. require "rubygems" gem "builder", "~> 2.0" require "builder" require "tmpdir" require "optparse" require "stringio" include Builder $KCODE = "UTF8" # Extensions that adds support for member access syntax class Hash def type result = self["type"] return result unless result.nil? self[:type] end def type= (type) self[:type] = type end def id result = self["id"] return result unless result.nil? self[:id] end def id= (id) self[:id] = id end def methods result = self["methods"] return result unless result.nil? self[:methods] end def methods= (methods) self[:methods] = methods end def method_missing (method, *args) self.class.instance_eval do define_method(method) do |*args| if args.length > 0 #self[method[0 ... -1]] = args[0] self[eval(":#{method}"[0 ... -1])] = args[0] else result = self[method] return result unless result.nil? self[eval(":#{method}")] end end end if (method = method.id2name) =~ /=/ eval("self.#{method} (args.length < 2 ? args[0] : args)") else eval("self.#{method}") end end end # This Struct represents an Objective-C Framework Framework = Struct.new(:name, :headers, :path) do def initialize self.headers = [] self.name = "" self.path = "" end end # This Struct represents a C/Objective-C header HeaderFile = Struct.new(:name, :framework, :cftypes, :constants, :defines, :enums, :externs, :functions, :function_pointers, :imports, :inline_functions, :opaques, :path, :structs, :typedefs) do def initialize self.name = "" self.cftypes = [] self.constants = [] self.defines = [] self.enums = [] self.externs = [] self.framework = "" self.functions = [] self.function_pointers = {} self.imports = [] self.inline_functions = [] self.opaques = [] self.path = "" self.structs = [] self.typedefs = [] end end Category = Struct.new(:name, :class) do def initialize self.name = "" self.class = "" end end # This class scans the headers class HeaderScaner CPP = ['/usr/bin/cpp-4.0', '/usr/bin/cpp-3.3', '/usr/bin/cpp3'].find { |x| File.exist?(x) } raise "cpp not found" if CPP.nil? CPPFLAGS = "-D__APPLE_CPP__ -include /usr/include/AvailabilityMacros.h" CPPFLAGS << "-D__GNUC__" unless /\Acpp-4/.match(File.basename(CPP)) attr_accessor :frameworks, :headers, :do_64bit, :extra def initialize @extern_name = 'extern' @frameworks = [] @file_content = nil @headers = [] @classes = {} @protocols = {} @categories = {} #@informal_protocols = {} #@function_pointers = {} @do_64bit = false end def get_classes @classes end def get_protocols @protocols end def get_categories @categories end # def get_informal_protocols # @informal_protocols # end def cftypes (header) re = /typedef\s+(const\s+)?(struct\s*\w+\s*\*\s*)([^\s]+Ref)\s*;/ @cpp_result.scan(re).each do |m| header.cftypes << { :name => m[2], :type => m[1], :const => m[0] =~ /const/ ? true : false} end end def constants (header) tmp = header.externs.map do |i| constant?(i, true) end header.constants = tmp.flatten.compact end def defines (header) re = /#define\s+([^\s]+)\s+(\([^)]+\)|[^\s]+)\s*$/ @file_content.scan(re).each do |m| next unless !m[0].include?('(') && m[1] != '\\' header.defines << { :name => m[0], :value => m[1] } end end def enums (header) re = /\benum\b\s*(\w+\s+)?\{([^}]*)\}/ @cpp_result.scan(re).each do |m| enum = { :name => m[0], :members => [] } m[1].split(",").map do |i| name, value = i.split("=", 2).map do |x| x.strip end enum[:members] << { :name => name, :value => value } unless name.empty? || name[0] == ?# end header.enums << enum end end def function_pointers (header) re = /typedef\s+([\w\s\*]+)\s*\(\s*\*\s*(\w+)\s*\)\s*\(([^)]*)\)\s*;/ data = @cpp_result.scan(re) re = /typedef\s+([\w\s\*]+)\s*\(([^)]+)\)\s*;/ data |= @cpp_result.scan(re).map do |m| ary = m[0].split(/(\w+)$/) ary[1] << " *" ary << m[1] ary end data.each do |m| variadic = false name = m[1] args = m[2].split(",").map do |x| variadic = x =~ /\.\.\./ ? true : false if x.include?(" ") ptr = x.sub!(/\[\]\s*$/, "") x = x.sub(/\w+\s*$/, "").strip ptr ? x + "*" : x else x.strip end end args.delete("...") do args end type = "#{m[0]}(*)(#{args.join(', ')})" header.function_pointers[name] = get_function_pointer(type) header.function_pointers[name].variadic = variadic end end def get_function_pointer (fp) type = {} re = /(.+)\(\*\)\((.+)\)/ if fp =~ /(.+)\(\*\)\((.+)\)/ m1 = $1 m2 = $2 type.const = m1 =~ /const/ ? true : false m1.gsub!(/const/, "") type.return_type = m1.strip type.args = [] args = m2.split(",") args.each do |arg| const = arg =~ /const/ ? true : false arg.gsub!(/const/, "") arg.strip! arg.gsub!(/\s*\*/, "*") type.args << {:type => arg, :const => const} end end type end def typedefs (header) re = /^\s*typedef\s+(.+)\s+([\w\*]+)\s*;$/ data = @cpp_result data.scan(re).each do |m| str = m[0] + m[1] if str =~ /,/ i = str.index(",") i = str.rindex(" ", i) type = str[0 ... i] names = str[i .. -1] names.gsub!(/ /, "") names = names.split(",") names.each do |name| var = get_var_info(type + " " + name) header.typedefs << var if var end else var = get_var_info(m[0] + " " + m[1]) header.typedefs << get_var_info(m[0] + " " + m[1]) if var end end end def externs (header) re = /^\s*#{@extern_name}\s+\b(.*)\s*;.*$/ header.externs = @cpp_result.scan(re).map do |m| m[0].strip end end def imports (header) tmp = [] @file_content.each do |line| if line =~ /#(include|import) <(.+)\.h>/ next if $2 == header.name tmp << $2 elsif line =~ /@class(\s+)(\w+)(,|;)/ next if $2 == header.name if $3 == ";" tmp << header.framework + "/" + $2 unless header.framework == "" tmp << $2 if header.framework == "" elsif str = line[6 + $1.length ... -2] str.gsub!(" ", "") arr = str.split(",") arr.each do |s| tmp << header.framework + "/" + s unless header.framework == "" tmp << s if header.framework == "" end end end end header.imports = tmp.compact.uniq end # def informal_protocols (header) # methods(header, /^@(interface)\s+(\w+)\s*:?\s*(\w*)\s*(\([^)]+\))?/, @protocols, false) # end def classes (header) methods(header, /^@(interface)\s+(\w+)\s*:?\s*(\w*)\s*(\<([\w,\s]+)>)?(\s*\{|$)/, @classes, false) end def protocols (header) methods(header, /^@(protocol)\s+(\w+)\s*:?\s*(\w*)\s*(\<([\w,\s]+)>)?(\([^)]+\))?/, @protocols, true) end def categories (header) methods(header, /^@(interface)\s+(\w+)\s*:?\s*(\w*)\s*(\<([\w,\s]+)>)?(\([^)]+\))/, @categories, false, true) end def methods (header, regex, array, protocol, category = false) interface_re = regex end_re = /^@end/ body_re = /^[-+]\s*(\([^)]+\))?\s*([^:\s;]+)/ args_re = /\w+\s*:/ prop_re = /^@property\s*(\([^)]+\))?\s*([^;]+);$/ current_interface = current_category = nil i = 0 parent = nil protocols = nil cat = nil @cpp_result.each_line do |line| size = line.size line.strip! if md = interface_re.match(line) parent = nil protocols = nil current_category = md[6].delete("()").strip if md[6] current_interface = md[2] unless category current_interface = current_category if category parent = md[3] unless md[3] == "" protocols = md[5] if md[5] if category cat = Category.new if category cat.name = current_category cat.class = md[2] end elsif end_re.match(line) current_interface = current_category = nil elsif current_interface && md = prop_re.match(line) # Parsing Objective-C 2.0 properties if (a = md[2].split(/\s/)).length >= 2 && /^\w+$/.match(name = a[-1]) && (type = a[0 .. -2].join(" ")).index(",").nil? getter, setter = name, "set#{name[0].chr.upcase + name[1 .. -1]}" readonly = false if attributes = md[1] if md = /getter\s*=\s*(\w+)/.match(attributes) getter = md[1] end if md = /setter\s*=\s*(\w+)/.match(attributes) setter = md[1] end readonly = true if attributes.index("readonly") end typeinfo = VarInfo.new(type, "", "") array[current_interface] ||= {} unless category array[cat] ||= {} if category methods = (array[current_interface].methods ||= []) unless category methods = (array[cat].methods ||= []) if category methods << MethodInfo.new(typeinfo, getter, false, [], line) unless readonly methods << MethodInfo.new(VarInfo.new("void", "", ""), setter + ":", false, [typeinfo], line) end end elsif current_interface && (line[0] == ?+ || line[0] == ?-) mtype = line[0] data = @cpp_result[i .. -1] body_md = body_re.match(data) next if body_md.nil? rettype = body_md[1] ? body_md[1].delete("()") : "id" retval = VarInfo.new(rettype, "", "") args = [] selector = "" data = data[0 .. data.index(";")] args_data = [] data.scan(args_re) do |x| args_data << [$`, x, $'] end variadic = false args_data.each_with_index do |ary, n| before, argname, argtype = ary arg_nameless = (n > 0 && /\)\s*$/.match(before)) argname = ":" if arg_nameless realargname = nil if n < args_data.length - 1 argtype.sub!(args_data[n + 1][2], "") if arg_nameless argtype.sub!(/(\w+\s*)?\w+\s*:\s*$/, "") else unless argtype.sub!(/(\w+)\s+\w+:\s*$/) { |s| realargname = $1; "" } # maybe the next argument is nameless argtype.sub!(/\w+\s*:\s*$/, "") end end else argtype.sub!(/\s+__attribute__\(\(.+\)\)/, "") if arg_nameless argtype.sub!(/\w+\s*;$/, "") else unless argtype.sub!(/(\w+)\s*;$/) { |s| realargname = $1; "" } variadic = argtype.sub!(/,\s*\.\.\.\s*;/, "") != nil argtype.sub!(/\w+\s*$/, "") if variadic end end end selector << argname realargname ||= argname.sub(/:/, "") args << VarInfo.new(argtype, realargname, "") unless argtype.empty? end selector = body_md[2] if selector.empty? args << VarInfo.new("...", "vararg", "") if variadic method = MethodInfo.new(retval, selector, line[0] == ?+, args, data) # if protocol && current_category && current_interface == "NSObject" # (@informal_protocols[current_category] ||= []) << method # end array[current_interface] ||= {} unless category array[cat] ||= {} if category if header.name == current_interface array[current_interface].file = header.name unless category array[cat].file = header.name if category else array[current_interface].file ||= header.name unless category array[cat].file ||= header.name if category end unless parent == current_interface || parent =~ /\s+/ || parent.nil? array[current_interface].parent = parent unless category array[cat].parent = parent if category end unless protocols.nil? protocols.gsub!(/ /, "") array[current_interface].protocols = protocols unless category array[cat].protocols = protocols if category end (array[current_interface].methods ||= []) << method unless category (array[cat].methods ||= []) << method if category end i += size end end def structs (header) re = /typedef\s+struct\s*\w*\s*((\w+)|\{([^{}]*(\{[^}]+\})?)*\}\s*([^\s]+))\s*(__attribute__\(.+\))?\s*((.+);|;)/ i = 0 body = nil @cpp_result.scan(re).each do |m| #name = m[4] #name = m[5] if name.nil? || name =~ /__attribute__/ name = "" if !m[7].nil? && m[7] !~ /typedef/ && m[7] !~ /\W/ name = m[7] elsif !m[4].nil? && m[4] !~ /__attribute__/ name = m[4] else name = m[5] end unless name.nil? name.gsub!(/\W/, "") name = name[6 .. -1] if name.length > 6 && name[0 ... 6] == "packed" name = name[7 .. -1] if name.length > 7 && name[0 ... 7] == "typedef" end struct = { :name => name, :members => [] } unless struct[:name].nil? if struct[:name][0, 1] == "*" struct[:name].sub!("*", "") end end return_type = nil stripped_return_type = nil body = m[2] if m[2] m[2].split(/,|;/).map do |i| str, bytes = i.split(":", 2).map do |x| x.strip end var = get_var_info(str, true) if var if var.return_type == "***dummy***" var.return_type = return_type var.stripped_return_type = stripped_return_type else return_type = var.return_type stripped_return_type = var.stripped_return_type end struct[:members] << { :name => var.name, :bytes => bytes, :declaredType => var.return_type, :type => "", :type64 => "" } unless str.empty? || str[0] == ?# names = [] tmp = struct[:members].collect do |member| unless names.include?(member[:name]) names << member[:name] member end end struct[:members] = tmp.compact end end end header.structs << struct if body end end def structs2 (header) re = /^struct\s*(\w+)\s*((\w+)|\{([^{}]*(\{[^}]+\})?)*\}\s*([^\s]+))$/ i = 0 body = nil @cpp_result.scan(re).each do |m| name = m[0] struct = { :name => name, :members => [] } return_type = nil stripped_return_type = nil body = m[3] if m[3] m[3].split(/,|;/).map do |i| str, bytes = i.split(":", 2).map do |x| x.strip end var = get_var_info(str, true) if var if var.return_type == "***dummy***" var.return_type = return_type var.stripped_return_type = stripped_return_type else return_type = var.return_type stripped_return_type = var.stripped_return_type end struct[:members] << { :name => var.name, :bytes => bytes, :declaredType => var.return_type, :type => "", :type64 => "" } unless str.empty? || str[0] == ?# names = [] tmp = struct[:members].collect do |member| unless names.include?(member[:name]) names << member[:name] member end end struct[:members] = tmp.compact end end end header.structs << struct if body end end def functions (header, inline = false) if inline inline_func_re = /(inline|__inline__)\s+((__attribute__\(\([^)]*\)\)\s+)?([\w\s\*<>]+)\s*\(([^)]*)\)\s*)\{/ res = @cpp_result.scan(inline_func_re) res.each do |x| x.delete_at(0) x.delete_at(1) end else skip_inline_re = /(static)?\s(inline|__inline__)[^{;]+(;|\{([^{}]*(\{[^}]+\})?)*\})\s*/ func_re = /(^([\w\s\*<>]+)\s*\(([^)]*)\)\s*)(__attribute__[^;]+)?;/ res = @cpp_result.gsub(skip_inline_re, '').scan(func_re) end funcs = res.map do |m| orig, base, args = m base.sub!(/^.*extern\s/, "") func = constant?(base) if func args = args.strip.split(",").map do |i| constant?(i) end next if args.any? do |x| x.nil? end args = [] if args.size == 1 && args[0].return_type == "void" FunctionInfo.new(func, args, orig, inline) end end.compact if inline header.inline_functions = funcs else header.functions = funcs end end def prepare (path) @file_content = File.read(path) @file_content.gsub!(%r{(/\*([^*]|[\r\n]|(\*+([^*/]|[\r\n])))*\*+/)|(//.*)}, ""); @complete_cpp_result, @cpp_result = do_cpp(path, false, true, "") end def scan (frameworks, headers) @frameworks = frameworks @headers = headers @frameworks.each do |framework| framework.headers.each do |header| prepare(header.path) imports(header) cftypes(header) externs(header) constants(header) enums(header) structs(header) structs2(header) typedefs(header) functions(header) functions(header, true) defines(header) classes(header) protocols(header) #informal_protocols(header) categories(header) function_pointers(header) end end @headers.each do |header| prepare(header.path) imports(header) cftypes(header) externs(header) constants(header) enums(header) structs(header) structs2(header) typedefs(header) functions(header) functions(header, true) defines(header) classes(header) protocols(header) #informal_protocols(header) categories(header) function_pointers(header) end end def get_var_info (str, multi = false) str.strip! return nil if str.empty? if str == "..." VarInfo.new("...", "...", str) else str = "***dummy*** " + str if str[-1].chr == '*' || str.index(/\s/).nil? tokens = multi ? str.split(',') : [str] part = tokens.first re = /^([^()]*)\b(\w+)\b\s*(\[[^\]]*\])*$/ m = re.match(part) if m return nil if m[1].split(/\s+/).any? do |x| ['end', 'typedef'].include?(x) end m = m.to_a[1..-1].compact.map do |i| i.strip end m[0] += m[2] if m.size == 3 m[0] = 'void' if m[1] == 'void' var = begin VarInfo.new(m[0], m[1], part) rescue return nil end if tokens.size > 1 [var, *tokens[1..-1].map { |x| constant?(m[0] + x.strip.sub(/^\*+/, '')) }] else var end end end end def constant? (str, multi = false) str.strip! return nil if str.empty? if str == '...' VarInfo.new('...', '...', str) else str << " dummy" if str[-1].chr == '*' or str.index(/\s/).nil? tokens = multi ? str.split(',') : [str] part = tokens.first re = /^([^()]*)\b(\w+)\b\s*(\[[^\]]*\])*$/ m = re.match(part) if m return nil if m[1].split(/\s+/).any? do |x| ['end', 'typedef'].include?(x) end m = m.to_a[1..-1].compact.map do |i| i.strip end m[0] += m[2] if m.size == 3 m[0] = 'void' if m[1] == 'void' var = begin VarInfo.new(m[0], m[1], part) rescue return nil end if tokens.size > 1 [var, *tokens[1..-1].map { |x| constant?(m[0] + x.strip.sub(/^\*+/, '')) }] else var end end end end class VarInfo attr_reader :name, :orig, :const, :type_modifier attr_accessor :octype, :resolved_type, :resolved_type64, :return_type, :stripped_return_type def initialize (type, name, orig) @return_type = type.clone @name = name @orig = orig @const = false @type_modifier = "" @return_type.gsub!(/\[[^\]]*\]/, "*") if @return_type =~ /\bconst\b/ @const = true @return_type.gsub!("const", "") end if @return_type =~ /\b(in|out|inout|oneway|bycopy|byref)\b/ case $1 when "in" @type_modifier << "n" when "out" @type_modifier << "o" when "inout" @type_modifier << "N" when "oneway" @type_modifier << "w" when "bycopy" @type_modifier << "c" when "byref" @type_modifier << "r" end @return_type.gsub!("#{$1}", "") end @return_type.gsub!(/\s?\*\s?/, "*") @return_type.gsub!(/^\(|\)$/, "") @return_type.strip! t = type.gsub(/\b(__)?const\b/, "") t.gsub!(/<[^>]*>/, '') t.gsub!(/\b(in|out|inout|oneway|const)\b/, "") t.gsub!(/\b__private_extern__\b/, "") t.gsub!(/^\s*\(?\s*/, "") t.gsub!(/\s*\)?\s*$/, "") raise "Empty type (was '#{type}')" if t.empty? @stripped_return_type = t end # def function_pointer? (function_pointers) # type = @function_pointers[@stripped_return_type] || @stripped_return_type # @function_pointer ||= FunctionPointerInfo.new_from_type(type) # end def <=>(x) self.name <=> x.name end def hash @name.hash end def eql? (o) @name == o.name end end class FunctionInfo < VarInfo attr_reader :args, :argc def initialize (function, args, orig, inline = false) super(function.return_type, function.name, orig) @args = args @argc = @args.size @variadic = false if @args[-1] && @args[-1].return_type == "..." @argc -= 1 @variadic = true @args.pop end @inline = inline self end def variadic? @variadic end def inline? @inline end end class FunctionPointerInfo < FunctionInfo def initialize (return_type, arg_types, orig) args = arg_types.map do |x| VarInfo.new(x, "", "") end super(VarInfo.new(return_type, "", ""), args, orig) end def self.new_from_type (type) @cache ||= {} info = @cache[type] return info if info tokens = type.split(/\(\*\)/) return nil if tokens.size != 2 return_type = tokens.first.strip rest = tokens.last.sub(/^\s*\(\s*/, "").sub(/\s*\)\s*$/, "") arg_types = rest.split(/,/).map do |x| x.strip end @cache[type] = self.new(return_type, arg_types, type) end end class MethodInfo < FunctionInfo attr_reader :selector def initialize (method, selector, is_class, args, orig) super(method, args, orig) @selector = selector @is_class = is_class self end def class_method? @is_class end def <=>(o) @selector <=> o.selector end def hash @selector.hash end def eql? (o) @selector == o.selector end end def do_cpp (path, fails_on_error = true, do_64 = true, flags = "") f_on = false err_file = '/tmp/.cpp.err' cpp_line = "#{CPP} #{CPPFLAGS} #{flags} #{do_64 ? '-D__LP64__' : ''} \"#{path}\" 2>#{err_file}" complete_result = `#{cpp_line}` if $?.to_i != 0 && fails_on_error $stderr.puts File.read(err_file) File.unlink(err_file) raise "#{CPP} returned #{$?.to_int/256} exit status\nline was: #{cpp_line}" end result = complete_result.select do |s| # First pass to only grab non-empty lines and the pre-processed lines # only from the target header (and not the entire pre-processing result). next if s.strip.empty? m = %r{^#\s*\d+\s+"([^"]+)"}.match(s) f_on = (File.basename(m[1]) == File.basename(path)) if m f_on end.select do |s| # Second pass to ignore all pro-processor comments that were left. /^#/.match(s) == nil end.join File.unlink(err_file) return [complete_result, result] end end class DStepGenerator VERSION = 1.0 attr_accessor :out_file, :scaner, :inject_path, :dependencies_switch, :options, :exclude, :frameworks_do_add def initialize @do_64bit = false @frameworks = [] @framework_paths = [] @headers = [] @import_directives = "#import <Foundation/Foundation.h>\n" #@informal_protocols = [] @classes = [] @protocols = [] @categories = [] @scaner = HeaderScaner.new @scaner.do_64bit = @do_64bit @umbrella_framework = nil @handled_dependencies = [] @dependencies_switch = false @options = [] @extra = false @exclude = [] @frameworks_do_add = [] # link to foundation framework by default @compiler_flags = "-framework Foundation" end def do_64bit return @do_64bit end def do_64bit= (do_64bit) @do_64bit = do_64bit @scaner.do_64bit = do_64bit end def extra @extra end def extra= (extra) @extra = extra @scaner.extra = extra end def collect @frameworks_do_add.each do |framework| add_framework(framework) end scaner.scan(@frameworks, @headers) @classes = scaner.get_classes @protocols = scaner.get_protocols @categories = scaner.get_categories #@informal_protocols = scaner.get_informal_protocols end def handle_framework (framework, sub_framework = false, parent_framework = nil) val = framework.name path = framework_path(val) raise "Can't locate framework '#{val}'" if path.nil? @framework_paths << File.dirname(path) raise "Can't find framework '#{val}'" if path.nil? parent_path, name = path.scan(/^(.+)\/(\w+)\.framework\/?$/)[0] if @private headers_path = File.join(path, "PrivateHeaders") raise "Can't locate private framework headers at '#{headers_path}'" unless File.exist?(headers_path) headers = Dir.glob(File.join(headers_path, "**", "*.h")) public_headers_path = File.join(path, "Headers") public_headers = if File.exist?(public_headers_path) HeaderScaner::CPPFLAGS << " -I#{public_headers_path} " Dir.glob(File.join8(headers_path, "**", "*.h")) else [] end else headers_path = File.join(path, "Headers") raise "Can't locate public framework headers at '#{headers_path}'" unless File.exist?(headers_path) public_headers = headers = Dir.glob(File.join(headers_path, "**", "*.h")) end # We can't just "#import <x/x.h>" as the main Framework header might not include _all_ headers. # So we are tricking this by importing the main header first, then all headers. header_basenames = (headers | public_headers).map do |x| x.sub(/#{headers_path}\/*/, "") end if idx = header_basenames.index("#{name}.h") header_basenames.delete_at(idx) header_basenames.unshift("#{name}.h") end if sub_framework pp = framework_path(parent_framework.name) tmp, pname = pp.scan(/^(.+)\/(\w+)\.framework\/?$/)[0] exclude = false @exclude.each do |e| exclude = true if pname =~ /#{e}/ end header_path = "#{pp}/Frameworks/#{name}.framework/Headers/#{name}.h" @import_directives << '#import "' + header_path + '"' if File.exist?(header_path) && !exclude exclude = false else @import_directives << header_basenames.map do |x| exclude = false @exclude.each do |e| exclude = true if x =~ /#{e}/ end next if exclude "#import <#{name}/#{x}>" end.join("\n") end @import_directives << "\n" @compiler_flags << " -F\"#{parent_path}\" -framework #{name}" unless sub_framework?(framework.name) @cpp_flags ||= "" @cpp_flags << "-F\"#{parent_path}\" " headers.each do |header| header_file = HeaderFile.new header_file.path = header header_file.name = File.basename(header, File.extname(header)) header_file.framework = framework.name framework.headers << header_file end # Memorize the dependencies. @dependencies = DStepGenerator.dependencies_of_framework(path) handle_sub_frameworks(framework) end def sub_framework? (framework) i = framework.index("framework") return false if i.nil? !framework.index("framework", i + 1).nil? end def handle_sub_frameworks (framework) path = framework_path(framework.name) @compiler_flags << " -B #{path}/Headers/ " frameworks_path = File.join(path, "Frameworks") frameworks = Dir.glob(File.join(frameworks_path, "*.framework")) frameworks.each do |f| add_framework(f, true, framework) end end def framework_path (val) return val if File.exist?(val) val += ".framework" unless /\.framework$/.match(val) paths = ["/System/Library/Frameworks", "/Library/Frameworks", "#{ENV['HOME']}/Library/Frameworks"] paths << "/System/Library/PrivateFrameworks" if @private paths.each do |dir| path = File.join(dir, val) return path if File.exist?(path) end return nil end def umbrella_framework (val) unless val == "" || val.nil? @umbrella_framework = val unless @compiler_flags.nil? || @compiler_flags == "" i = @compiler_flags.index("-framework") @compiler_flags = @compiler_flags[0 .. i - 1] + "-framework #{val}" else @compiler_flags = "-framework #{val}" end end end def self.dependencies_of_framework (path) @dependencies ||= {} name = File.basename(path, ".framework") path = File.join(path, name) deps = @dependencies[path] if deps.nil? deps = `otool -L "#{path}"`.scan(/\t([^\s]+)/).map do |m| dpath = m[0] next if File.basename(dpath) == name next if dpath.include?("PrivateFrameworks") next unless dpath.sub!(/\.framework\/Versions\/\w+\/\w+$/, "") dpath + ".framework" end.compact @dependencies[path] = deps end return deps end def compile_and_execute (code, enable_64bit = false, clean_when_fail = false) compiler_line = "gcc " src = File.new(unique_tmp_path("src", ".m"), "w") src << code src.close arch_flag = if enable_64bit "-arch x86_64 -arch ppc64" else "-arch i386 -arch ppc" end compiler_line << arch_flag bin = unique_tmp_path "bin", "" log = unique_tmp_path "log", ".txt" @options.each do |option| @compiler_flags << " #{option} " end line = "#{compiler_line} -o #{bin} #{src.path} #{@compiler_flags} 2>#{log}" unless system(line) msg = "Cannot compile Objective-C code ...aborting\nCommand was: #{line}\n\nLog:\n#{File.read(log)}\n\n" $stderr << msg File.delete src.path if clean_when_fail raise msg end result = `#{bin}` unless $?.success? raise "Cannot execute compiled Objective-C code ... aborting\nCommand was: #{line}\nBinary is: #{bin}" end File.delete bin File.delete log File.delete src.path return result end def unique_tmp_path (base, extension = "", dir = Dir.tmpdir) i = 0 loop do path = File.join(dir, "#{base}-#{Process.pid}-#{i}#{extension}") return path unless File.exists?(path) i += 1 end end def add_header (path) header = HeaderFile.new header.path = path header.name = File.basename(path, File.extname(path)) @import_directives << "#include <#{path}>\n" @headers << header end def add_framework (name, sub_framework = false, parent_framework = nil) framework = Framework.new framework.name = name handle_framework(framework, sub_framework, parent_framework) #@import_directives << "#import <#{framework.name}/#{framework.name}.h>\n" @frameworks << framework end def collect_cftypes (header, enable_64bit) types = [] header.cftypes.each do |cftype| types << cftype[:type] end resolved_types = resolve_types_helper(types, enable_64bit) if types.length > 0 header.cftypes.each_with_index do |cftype, i| cftype[enable_64bit ? :type64 : :type] = resolved_types[i] end end def collect_constants (header, enable_64bit) types = [] header.constants.each do |constant| types << constant.stripped_return_type end resolved_types = resolve_types_helper(types, enable_64bit) if types.length > 0 header.constants.each_with_index do |constant, i| constant.resolved_type = resolved_types[i] unless enable_64bit constant.resolved_type64 = resolved_types[i] if enable_64bit end end def collect_structs (header, enable_64bit) types = [] header.structs.each do |struct| types << struct[:name] struct[:members].each do |member| types << member[:declaredType] end end resolved_types = resolve_types_helper(types, enable_64bit) if types.length > 0 i = 0 header.structs.each do |struct| struct[enable_64bit ? :type64 : :type] = resolved_types[i] i += 1 struct[:members].each do |member| member[enable_64bit ? :type64 : :type] = resolved_types[i] i += 1 end end end def collect_typedefs (header, enable_64bit) types = [] header.typedefs.each do |typedef| types << typedef.stripped_return_type end resolved_types = resolve_types_helper(types, enable_64bit) if types.length > 0 header.typedefs.each_with_index do |typedef, i| typedef.resolved_type = resolved_types[i] unless enable_64bit typedef.resolved_type64 = resolved_types[i] if enable_64bit end end def collect_functions (header, enable_64bit) types = [] header.functions.each do |function| types << function.stripped_return_type function.args.each do |arg| types << arg.stripped_return_type end end resolved_types = resolve_types_helper(types, enable_64bit) if types.length > 0 i = 0 header.functions.each do |function| function.resolved_type = resolved_types[i] unless enable_64bit function.resolved_type64 = resolved_types[i] if enable_64bit i += 1 function.args.each do |arg| arg.resolved_type = resolved_types[i] unless enable_64bit arg.resolved_type64 = resolved_types[i] if enable_64bit i += 1 end end end def collect_functions_pointers (header, enable_64bit) types = [] header.function_pointers.each do |fp, value| types << value.return_type value.args.each do |arg| types << arg.type end unless value.args.nil? end resolved_types = resolve_types_helper(types, enable_64bit) if types.length > 0 i = 0 header.function_pointers.each do |fp, value| value.resolved_type = resolved_types[i] unless enable_64bit value.resolved_type64 = resolved_types[i] if enable_64bit i += 1 value.args.each do |arg| arg.resolved_type = resolved_types[i] unless enable_64bit arg.resolved_type64 = resolved_types[i] if enable_64bit i += 1 end unless value.args.nil? end end def collect_header_types (header, enable_64bit) collect_cftypes(header, enable_64bit) collect_constants(header, enable_64bit) collect_structs(header, enable_64bit) collect_typedefs(header, enable_64bit) collect_functions(header, enable_64bit) collect_functions_pointers(header, enable_64bit) end def collect_classes_types (enable_64bit) types = [] @classes.each do |clazz, value| value.methods.each do |method| types << method.stripped_return_type method.args.each do |arg| types << arg.stripped_return_type end end end resolved_types = resolve_types_helper(types, enable_64bit) if types.length > 0 i = 0 @classes.each do |clazz, value| value.methods.each do |method| method.resolved_type = resolved_types[i] unless enable_64bit method.resolved_type64 = resolved_types[i] if enable_64bit i += 1 method.args.each do |arg| arg.resolved_type = resolved_types[i] unless enable_64bit arg.resolved_type64 = resolved_types[i] if enable_64bit i += 1 end end end end def collect_protocols_types (enable_64bit) types = [] @protocols.each do |protocol, value| value.methods.each do |method| types << method.stripped_return_type method.args.each do |arg| types << arg.stripped_return_type end end end resolved_types = resolve_types_helper(types, enable_64bit) if types.length > 0 i = 0 @protocols.each do |protocol, value| value.methods.each do |method| method.resolved_type = resolved_types[i] unless enable_64bit method.resolved_type64 = resolved_types[i] if enable_64bit i += 1 method.args.each do |arg| arg.resolved_type = resolved_types[i] unless enable_64bit arg.resolved_type64 = resolved_types[i] if enable_64bit i += 1 end end end end def collect_categories_types (enable_64bit) types = [] @categories.each do |category, value| value.methods.each do |method| types << method.stripped_return_type method.args.each do |arg| types << arg.stripped_return_type end end end resolved_types = resolve_types_helper(types, enable_64bit) if types.length > 0 i = 0 @categories.each do |category, value| value.methods.each do |method| method.resolved_type = resolved_types[i] unless enable_64bit method.resolved_type64 = resolved_types[i] if enable_64bit i += 1 method.args.each do |arg| arg.resolved_type = resolved_types[i] unless enable_64bit arg.resolved_type64 = resolved_types[i] if enable_64bit i += 1 end end end end def get_framework_name (framework) i = framework.rindex(".framework") return framework if i.nil? x = framework.rindex("/", i) framework[x + 1 ... i] end def resolve_types (enable_64bit = false) @frameworks.each do |framework| framework.headers.each do |header| collect_header_types(header, enable_64bit) end end @headers.each do |header| collect_header_types(header, enable_64bit) end collect_classes_types(enable_64bit) collect_protocols_types(enable_64bit) collect_categories_types(enable_64bit) end def resolve_types_helper (types, enable_64bit) code = "#include <stdio.h>\n" code << @import_directives @frameworks.each do |framework| framework.headers.each do |header| exclude = false @exclude.each do |e| exclude = true if e =~ header.name end if framework.name[0, 1] == "/" code << "#import \"#{framework.name}/Headers/#{header.name}.h\"\n" unless header.name == "xmmintrin" else code << "#import <#{framework.name}/#{header.name}.h>\n" unless header.name == "xmmintrin" end unless exclude types << collect_header_types(header, enable_64bit) exclude = false end end if @extra @headers.each do |header| exclude = false @exclude.each do |e| exclude = true if e =~ header.name end code << "#import <#{header.name}.h>\n" unless exclude || header.name == "xmmintrin" exclude = false end if @extra File.foreach(@inject_path) do |line| code << line end if !@inject_path.nil? && File.exist?(@inject_path) code << "\n\n" code << "int main ()\n{\n" types.flatten! types.each do |type| code << ' printf("%s\n", ' + "@encode(__typeof__(#{type})));\n" unless type.nil? || type.length == 0 end code << " return 0;\n}" resolved_types = [] compile_and_execute(code, enable_64bit).split("\n").each do |line| resolved_types << line end resolved_types # i = 0 # # @frameworks.each do |framework| # framework.headers.each do |header| # i = resolve_header_types(header, enable_64bit, resolved_types, i) # end # end # # @headers.each do |header| # i = resolve_header_types(header, enable_64bit, resolved_types, i) # end # # i = resolve_classes_types(enable_64bit, resolved_types, i) # i = resolve_protocols_types(enable_64bit, resolved_types, i) # #i = resolve_informal_protocols_types(enable_64bit, resolved_types, i) # i = resolve_categories_types(enable_64bit, resolved_types, i) end def generate_header (xml, header) xml.file :name => header.name do header.imports.each do |import| xml.import import end header.defines.each do |define| xml.define define end header.cftypes.each do |cftype| xml.cftype cftype end header.constants.each do |constant| xml.constant :name => constant.name, :declaredType => constant.return_type, :type => constant.resolved_type, :type64 => constant.resolved_type64, :const => constant.const end header.enums.each do |enum| xml.enum :name => enum[:name] do enum[:members].each do |member| xml.member member end end end header.structs.each do |struct| xml.struct :name => struct[:name], :type => struct[:type], :type64 => struct[:type64] do struct[:members].each do |member| xml.member member end end end header.typedefs.each do |typedef| xml.typedef :name => typedef.name, :declaredType => typedef.return_type, :type => typedef.resolved_type, :type64 => typedef.resolved_type64, :const => typedef.const end header.functions.each do |function| xml.function :name => function.name, :inline => function.inline?, :variadic => function.variadic? do function.args.each do |arg| xml.arg :name => arg.name, :declaredType => arg.return_type, :type => arg.resolved_type, :type64 => arg.resolved_type64, :const => arg.const, :typeModifier => arg.type_modifier end xml.returnValue :declaredType => function.return_type, :type => function.resolved_type, :type64 => function.resolved_type64, :const => function.const, :typeModifier => function.type_modifier end end header.function_pointers.each do |fp, value| xml.functionPointer :name => fp, :variadic => value.variadic do value.args.each do |arg| xml.arg :declaredType => arg.type, :type => arg.resolved_type, :type64 => arg.resolved_type64, :const => arg.const end unless value.args.nil? xml.returnValue :declaredType => value.return_type, :type => value.resolved_type, :type64 => value.resolved_type64, :const => value.const end end end end def generate_classes (xml) @classes.each do |clazz, value| xml.class :name => clazz, :parent => value.parent, :protocols => value.protocols, :file => value.file do value.methods.each do |method| xml.method :selector => method.selector, :classMethod => method.class_method?, :variadic => method.variadic? do method.args.each do |arg| xml.arg :name => arg.name, :declaredType => arg.return_type, :type => arg.resolved_type, :type64 => arg.resolved_type64, :const => arg.const, :typeModifier => arg.type_modifier end xml.returnValue :declaredType => method.return_type, :type => method.resolved_type, :type64 => method.resolved_type64, :const => method.const, :typeModifier => method.type_modifier end end end end end def generate_protocols (xml) @protocols.each do |protocol, value| xml.protocol :name => protocol, :parent => value.parent, :protocols => value.protocols, :file => value.file do value.methods.each do |method| xml.method :selector => method.selector, :classMethod => method.class_method?, :variadic => method.variadic? do method.args.each do |arg| xml.arg :name => arg.name, :declaredType => arg.return_type, :type => arg.resolved_type, :type64 => arg.resolved_type64, :const => arg.const, :typeModifier => arg.type_modifier end xml.returnValue :declaredType => method.return_type, :type => method.resolved_type, :type64 => method.resolved_type64, :const => method.const, :typeModifier => method.type_modifier end end end end end # def generate_informal_protocols (xml) # @informal_protocols.each do |name, methods| # xml.informalProtocol :name => name do # methods.each do |method| # xml.method :selector => method.selector, :classMethod => method.class_method?, :variadic => method.variadic? do # method.args.each do |arg| # xml.arg :name => arg.name, :declaredType => arg.return_type, :type => arg.resolved_type, :type64 => arg.resolved_type64, :const => arg.const, :typeModifier => arg.type_modifier # end # # xml.returnValue :declaredType => method.return_type, :type => method.resolved_type, :type64 => method.resolved_type64, :const => method.const, :typeModifier => method.type_modifier # end # end # end # end # end def generate_categories (xml) @categories.each do |category, value| xml.category :name => category.name, :class => category.class, :parent => value.parent, :protocols => value.protocols, :file => value.file do value.methods.each do |method| xml.method :selector => method.selector, :classMethod => method.class_method?, :variadic => method.variadic? do method.args.each do |arg| xml.arg :name => arg.name, :declaredType => arg.return_type, :type => arg.resolved_type, :type64 => arg.resolved_type64, :const => arg.const, :typeModifier => arg.type_modifier end xml.returnValue :declaredType => method.return_type, :type => method.resolved_type, :type64 => method.resolved_type64, :const => method.const, :typeModifier => method.type_modifier end end end end end def generate resolve_types resolve_types(true) if @do_64bit file = STDOUT if @out_file == nil file = File.open @out_file, "w" unless @out_file == nil xml = XmlMarkup.new(:target => file, :indent => 4) xml.instruct! xml.dstep :xmlns => "http://www.dsource.org/projects/dstep", "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance", "xsi:schemaLocation" => "http://www.dsource.org/projects/dstep/trunk/scripts/dstep.xsd" do @frameworks.each do |framework| xml.framework :name => framework.name do framework.headers.each do |header| generate_header(xml, header) end end end @headers.each do |header| generate_header(xml, header) end generate_classes(xml) generate_protocols(xml) #generate_informal_protocols(xml) generate_categories(xml) end file.close unless file == STDOUT end def write_dependencies @frameworks_do_add.each do |framework| add_framework(framework) end file = STDOUT if @out_file == nil file = File.open @out_file, "w" unless @out_file == nil @dependencies.each do |dep| file << dep + "\n" end file.close unless file == STDOUT exit 0 end end def die (*msg) $stderr.puts msg exit 1 end if __FILE__ == $0 dstep_gen = DStepGenerator.new OptionParser.new do |opts| opts.banner = "Usage: #{File.basename(__FILE__)} [options] <headers...>" opts.separator "" opts.separator "Options:" opts.on("-f", "--framework FRAMEWORK", "Generate metadata for the given framework.") do |opt| dstep_gen.frameworks_do_add << opt end opts.on("-u", "--umbrella FRAMEWORK", "Link againts the given umbrella framework.") do |opt| dstep_gen.umbrella_framework(opt) end opts.on(nil, "--64-bit", "Write 64-bit annotations.") do dstep_gen.do_64bit = true end opts.on("-o", "--output FILE", "Write output to the given file.") do |opt| die "Output file can't be specified more than once" if dstep_gen.out_file dstep_gen.out_file = opt end # opts.on("-d", "--output-dir PATH", "Write ouptut to the given paht, use this with the --framework option") do |opt| # die "Output directory can't be specified more than once" if dstep_gen.out_dir # dstep_gen.out_dir = opt # end opts.on("-c", "--code FILE", "The path to a file with code to inject") do |opt| dstep_gen.inject_path = opt end opts.on("-d", "--dependencies", "Write framework dependencies and exit") do |opt| dstep_gen.dependencies_switch = true end opts.on("-s", "--option OPTION", "Pass OPTION to the compiler") do |opt| dstep_gen.options << opt end opts.on("-e", "--extra", "Included extra headers") do |opt| dstep_gen.extra = true end opts.on("-x", "--exclude HEADER", "Exlude the given header file") do |opt| dstep_gen.exclude << opt end help_msg = "Use the `-h' flag or for help." opts.on("-h", "--help", "Show this message.") do puts opts, help_msg exit end opts.on('-v', '--version', 'Show version.') do puts DStepGenerator::VERSION exit end opts.separator "" if ARGV.empty? die opts.banner else #begin opts.parse!(ARGV) dstep_gen.write_dependencies if dstep_gen.dependencies_switch ARGV.each do |header| dstep_gen.add_header(header) end dstep_gen.collect dstep_gen.generate # rescue => e # msg = e.message # msg = "Internal error" if msg.empty? # # die msg, opts.banner, help_msg # end end end end