XCODE_PLATFORMS_DIR = ENV['xcode_platforms_dir'] IOS_SDK_VERSIONS = ENV['ios_sdk_versions'].split(',') OSX_SDK_VERSIONS = ENV['osx_sdk_versions'].split(',') ANDROID_SDK = ENV['android_sdk'] ANDROID_API_VERSIONS = ENV['android_api_versions'].split(',') ANDROID_ARCHS = ENV['android_archs'].split(',') verbose(true) NEED_STRIP = !ENV['DEBUG'] task :default => :all task :all => [:vm_files, :bridgesupport_files, :bridgesupport_static_stubs] task :vm_files do strip = File.join(XCODE_PLATFORMS_DIR, '../Toolchains/XcodeDefault.xctoolchain/usr/bin/strip') first = true OSX_SDK_VERSIONS.each do |sdk_version| sdk = File.join('osx', sdk_version, 'MacOSX') mkdir_p sdk objs = "../vm/MacOSX#{sdk_version}.objs" Dir.glob(File.join(objs, 'kernel-*.bc')).each do |path| install path, sdk end install File.join(objs, 'librubymotion-static.a'), sdk if first and ENV['SDK_BETA'] == nil objs = "../vm/MacOSX#{sdk_version}-repl.objs" install File.join(objs, 'librubymotion-repl.dylib'), 'osx' first = false end end IOS_SDK_VERSIONS.each do |sdk_version| ios = File.join('ios', sdk_version, 'iPhoneOS') mkdir_p ios objs = "../vm/iPhoneOS#{sdk_version}.objs" Dir.glob(File.join(objs, 'kernel-*.bc')).each do |path| install path, ios end install File.join(objs, 'librubymotion-static.a'), ios sim = File.join('ios', sdk_version, 'iPhoneSimulator') mkdir_p sim objs = "../vm/iPhoneSimulator#{sdk_version}.objs" Dir.glob(File.join(objs, 'kernel-*.bc')).each do |path| install path, sim end install File.join(objs, 'librubymotion-static.a'), sim end if IOS_SDK_VERSIONS.size > 1 and ENV['SDK_BETA'] == nil # Create a fat librubymotion-repl.dylib library for iOS simulator. The i386 slice has a deployment_target for 4.3 and the x86_64 slice has a deployment_target for 7.1. first_ios_repl = "../vm/iPhoneSimulator#{IOS_SDK_VERSIONS.first}-repl.objs/librubymotion-repl.dylib" # FIXME : Need to use latest SDK version instead of '7.1' raise "[!] Need to target at least the iOS 7.1 SDK." unless IOS_SDK_VERSIONS.include?('7.1') raise "[!] An iOS SDK older than 7.1 is required in order to build the REPL dylib." if IOS_SDK_VERSIONS.first == '7.1' last_ios_repl = "../vm/iPhoneSimulator7.1-repl.objs/librubymotion-repl.dylib" sh "/usr/bin/lipo -extract_family x86_64 \"#{last_ios_repl}\" -output /tmp/librubymotion-repl64.dylib" sh "/usr/bin/lipo -create \"#{first_ios_repl}\" /tmp/librubymotion-repl64.dylib -output ios/librubymotion-repl.dylib" end # XXX we are no longer stripping symbols since it apparently removes exception handlers # (iOS 7 only). Not a big deal since we do not compile with debug metadata by default (-g). # remove debug symbols #if NEED_STRIP # Dir.glob('{osx,ios}/**/*.{a,dylib}').each { |x| sh("\"#{strip}\" -S \"#{x}\"") } #end end CORE_MIDI_64_BIT_TYPES = <<-EOS EOS def apply_bridgesupport_fixes(bs, include_64bit_types) name = File.basename(bs, '.bridgesupport') rules = [] case name when 'CoreMIDI', 'AudioToolbox', 'AudioUnit' rules << [/\s*$/, CORE_MIDI_64_BIT_TYPES + ''] end else rules << [/" in base if lines.last.strip != "" io.puts "" end io.print merge.lines.to_a[2..-1].join # skip first "\n" in merge } end def apply_CoreText_fix(framework_path) header_path = File.join(framework_path, 'Headers', 'CTRunDelegate.h') content = File.read(header_path) signature = 'CFTypeID CTRunDelegateGetTypeID' message = "// Disabled by the RubyMotion build system: #{signature}" unless content.include?(message) content.sub!(/^#{signature}/, message) File.open(header_path, 'w') { |f| f << content } end end EXCLUDED_FRAMEWORKS = ['Kernel', 'System', 'IOKit', 'Ruby', 'RubyCocoa', 'vecLib'] task :bridgesupport_files do platform_dev_path = "#{XCODE_PLATFORMS_DIR}/MacOSX.platform/Developer" OSX_SDK_VERSIONS.each do |sdk_version| sdk_path = "#{platform_dev_path}/SDKs/MacOSX#{sdk_version}.sdk" sdk_frameworks = "#{sdk_path}/System/Library/Frameworks" mkdir_p "osx/#{sdk_version}/BridgeSupport" mkdir_p "osx/#{sdk_version}/BridgeSupport/child" Dir.glob('*.bridgesupport').each { |bs| cp bs, "osx/#{sdk_version}/BridgeSupport" } Dir.glob('osx/*.bridgesupport').each { |bs| cp bs, "osx/#{sdk_version}/BridgeSupport" } generate_bridgesupport_file = lambda do |sdk_path, sdk_version, framework_path, is_handle_nested| framework = File.basename(framework_path, '.framework') break if EXCLUDED_FRAMEWORKS.include?(framework) break unless File.exist?(File.join(framework_path, framework)) break unless File.exist?(File.join(framework_path, 'Headers')) dest = "osx/#{sdk_version}/BridgeSupport/#{framework}.bridgesupport" dest = "osx/#{sdk_version}/BridgeSupport/child/#{framework}.bridgesupport" if is_handle_nested linked_framework = case framework when 'HIToolbox', 'Ink' then 'Carbon' when 'CFNetwork', 'LaunchServices', 'Metadata', 'OSServices', 'SearchKit' then 'CoreServices' when 'ColorSync', 'CoreText', 'HIServices', 'ImageIO' then 'ApplicationServices' else framework end unless File.exist?(dest) a = sdk_version.scan(/(\d+)\.(\d+)/)[0] major = a[0].to_i minor = a[1].to_i if major <= 10 && minor <= 9 sdk_version_headers = "#{major}#{minor}0" else sdk_version_headers = "#{major}#{minor}00" end sh "../bin/gen_bridge_metadata --format complete --cflags \"-isysroot #{sdk_path} -mmacosx-version-min=#{sdk_version} -DTARGET_OS_MAC -D__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__=#{sdk_version_headers} -F#{File.dirname(framework_path)} -framework #{linked_framework}\" --framework #{framework_path} > #{dest}" apply_bridgesupport_fixes(dest, true) return dest end return dest if is_handle_nested end Dir.glob(File.join(sdk_frameworks, '*.framework')).each do |framework_path| base_bridgesupport_path = generate_bridgesupport_file.call(sdk_path, sdk_version, framework_path, false) if base_bridgesupport_path child_framework_paths = Dir.glob(File.join(framework_path, '/Frameworks/*.framework')) child_framework_paths.delete_if { |x| x.include?("CoreGraphics") } if sdk_version != "10.7" child_framework_paths.each do |child_framework_path| if sdk_version == "10.7" && child_framework_path.include?("CoreGraphics") dest = 'osx/10.7/BridgeSupport/child/CoreGraphics.bridgesupport' sh "../bin/gen_bridge_metadata --format complete --cflags \"-isysroot #{sdk_path} -F#{sdk_path}/System/Library/Frameworks/ApplicationServices.framework/Frameworks -mmacosx-version-min=10.7 -DTARGET_OS_MAC -D__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__=100700 -framework ApplicationServices\" --framework #{child_framework_path} > #{dest}" apply_bridgesupport_fixes(dest, true) merge_path = dest else merge_path = generate_bridgesupport_file.call(sdk_path, sdk_version, child_framework_path, true) end merge_bridgesupport(base_bridgesupport_path, merge_path) end end end end platform_dev_path = "#{XCODE_PLATFORMS_DIR}/iPhoneSimulator.platform/Developer" IOS_SDK_VERSIONS.each do |sdk_version| sdk_path = "#{platform_dev_path}/SDKs/iPhoneSimulator#{sdk_version}.sdk" sdk_frameworks = "#{sdk_path}/System/Library/Frameworks" mkdir_p "ios/#{sdk_version}/BridgeSupport" mkdir_p "ios/#{sdk_version}/BridgeSupport/child" Dir.glob('*.bridgesupport').each { |bs| cp bs, "ios/#{sdk_version}/BridgeSupport" } Dir.glob('ios/*.bridgesupport').each { |bs| cp bs, "ios/#{sdk_version}/BridgeSupport" } generate_bridgesupport_file = lambda do |sdk_path, sdk_version, framework_path, is_handle_nested| framework = File.basename(framework_path, '.framework') break if EXCLUDED_FRAMEWORKS.include?(framework) break unless File.exist?(File.join(framework_path, framework)) break unless File.exist?(File.join(framework_path, 'Headers')) dest = "ios/#{sdk_version}/BridgeSupport/#{framework}.bridgesupport" dest = "ios/#{sdk_version}/BridgeSupport/child/#{framework}.bridgesupport" if is_handle_nested unless File.exist?(dest) apply_CoreText_fix(framework_path) if framework == 'CoreText' a = sdk_version.scan(/(\d+)\.(\d+)/)[0] sdk_version_headers = ((a[0].to_i * 10000) + (a[1].to_i * 100)).to_s include_64bit_types = sdk_version >= '7.0' extra_flags = include_64bit_types ? '--64-bit' : '--no-64-bit' sh "../bin/gen_bridge_metadata --format complete #{extra_flags} --cflags \"-isysroot #{sdk_path} -miphoneos-version-min=#{sdk_version} -DTARGET_OS_IPHONE -D__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__=#{sdk_version_headers} -framework #{framework}\" --framework #{framework_path} > #{dest}" apply_bridgesupport_fixes(dest, include_64bit_types) if framework == 'Foundation' and sdk_version >= '7.0' # In iOS 7.0, NSObject is defined in /usr/include/objc/NSObject.h sh "../bin/gen_bridge_metadata --format complete #{extra_flags} --cflags \"-isysroot #{sdk_path} -miphoneos-version-min=#{sdk_version} -DTARGET_OS_IPHONE -D__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__=#{sdk_version_headers} -framework #{framework} -I#{sdk_path}/usr/include \" objc/NSObject.h objc/NSObjCRuntime.h > /tmp/NSObject.bridgesupport" merge_bridgesupport dest, '/tmp/NSObject.bridgesupport' end if framework == 'AudioToolbox' # Generate AudioUnit.bridgesupport and merge it into AudioToolbox.bridgesupport framework_path = File.join(framework_path, '../AudioUnit.framework') framework = File.basename(framework_path, '.framework') headers_path = File.join(framework_path, 'Headers') sh "../bin/gen_bridge_metadata --format complete #{extra_flags} --cflags \"-isysroot #{sdk_path} -miphoneos-version-min=#{sdk_version} -DTARGET_OS_IPHONE -D__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__=#{sdk_version_headers} -framework #{framework} -I#{headers_path}\" #{headers_path}/*.h > /tmp/AudioUnit.bridgesupport" apply_bridgesupport_fixes('/tmp/AudioUnit.bridgesupport', include_64bit_types) merge_bridgesupport dest, '/tmp/AudioUnit.bridgesupport' end return dest end return dest if is_handle_nested end Dir.glob(File.join(sdk_frameworks, '*.framework')).each do |framework_path| base_bridgesupport_path = generate_bridgesupport_file.call(sdk_path, sdk_version, framework_path, false) if base_bridgesupport_path child_framework_paths = Dir.glob(File.join(framework_path, '/Frameworks/*.framework')) child_framework_paths.each do |child_framework_path| merge_path = generate_bridgesupport_file.call(sdk_path, sdk_version, child_framework_path, true) merge_bridgesupport(base_bridgesupport_path, merge_path) end end end end Rake::Task['UIAutomation:generate_bridgesupport_files'].invoke end def generate_bs_static_stub(file, includes) require 'rubygems' require 'nokogiri' text = '' includes.each { |inc| text << "#import <#{inc}>\n" } gen_func_code = Proc.new do |node, declared_type_attribute| name = node['name'].to_s retval = node.xpath('./retval')[0][declared_type_attribute].to_s args = node.xpath('./arg').map { |node| node[declared_type_attribute].to_s } # # FIXME # # 'SCNMatrix4FromMat4', 'SCNMatrix4ToMat4', 'SCNVector3ToFloat3' and 'SCNVector4ToFloat4' inline functions # are added since iOS 8.OSX 10.10 beta3. # gen_bridge_metadata's clang (included in bridgesupportparser.bundle) is too old and # its clang does not generate the argument type of those functions properly. # # So, when compile the stub functions of those inline function, it causes a compile error as the following. # # osx/10.10/BridgeSupport/SceneKit_stubs.m:6:31: error: passing 'int' to parameter of incompatible type 'matrix_float4x4' # return SCNMatrix4FromMat4(arg0); # case name when 'SCNMatrix4FromMat4' args[0] = 'matrix_float4x4' retval = 'SCNMatrix4' when 'SCNMatrix4ToMat4' args[0] = 'SCNMatrix4' retval = 'matrix_float4x4' when 'SCNVector3ToFloat3' args[0] = 'SCNVector3' retval = 'vector_float3' when 'SCNVector4ToFloat4' args[0] = 'SCNVector4' retval = 'vector_float4' end if retval.strip.empty? nil else proto = "#{retval} __concrete__#{name}(" proto << (0...args.size).to_a.map { |i| "#{args[i]} arg#{i}" }.join(', ') proto << ")" func = '' func << proto func << "\n{\n " func << " return " if retval != 'void' func << name << '(' func << (0...args.size).to_a.map { |i| "arg#{i}" }.join(', ') func << ");\n}\n\n" func end end doc = Nokogiri::XML(File.read(file)) did_something = false doc.xpath("/signatures/function[@inline=\"true\"]").each do |node| code64 = gen_func_code.call(node, 'declared_type64') code32 = gen_func_code.call(node, 'declared_type') if code64 text << "\n#if defined(__LP64__)\n" text << code64 text << "\n#endif\n" end if code32 text << "\n#if !defined(__LP64__)\n" if code64 text << code32 text << "\n#endif\n" if code64 end did_something = true end did_something ? text : nil end task :bridgesupport_static_stubs do OSX_SDK_VERSIONS.each do |sdk_version| Dir.glob("osx/#{sdk_version}/BridgeSupport/*.bridgesupport") do |bs_path| framework = File.basename(bs_path).sub(/\.bridgesupport/, '') case framework when 'GLKit' # TODO next end code = "osx/#{sdk_version}/BridgeSupport/#{framework}_stubs.m" includes = case framework when 'OpenGLES' ['OpenGLES/EAGLDrawable.h'] else ['Cocoa/Cocoa.h', "#{framework}/#{framework}.h"] end unless File.exist?(code) text = generate_bs_static_stub(bs_path, includes) next unless text File.open(code, 'w') { |io| io.puts '#pragma GCC diagnostic ignored "-Wdeprecated-declarations"' io.write(text) } end obj = "osx/#{sdk_version}/MacOSX/#{framework}_stubs.o" next if File.exist?(obj) platform_dev = "#{XCODE_PLATFORMS_DIR}/MacOSX.platform/Developer" cflags = "-isysroot #{platform_dev}/SDKs/MacOSX#{sdk_version}.sdk" cflags << " -arch i386 -arch x86_64" cflags << " -mmacosx-version-min=#{sdk_version} -Wall -Werror -O3 -fobjc-abi-version=2 -fobjc-legacy-dispatch" sh "/usr/bin/clang #{cflags} #{code} -c -o #{obj}" end end IOS_SDK_VERSIONS.each do |sdk_version| Dir.glob("ios/#{sdk_version}/BridgeSupport/*.bridgesupport") do |bs_path| framework = File.basename(bs_path).sub(/\.bridgesupport/, '') code = "ios/#{sdk_version}/BridgeSupport/#{framework}_stubs.m" includes = case framework when 'OpenGLES' ['OpenGLES/EAGLDrawable.h'] else ['UIKit/UIKit.h', "#{framework}/#{framework}.h"] end unless File.exist?(code) text = generate_bs_static_stub(bs_path, includes) next unless text File.open(code, 'w') { |io| io.write(text) } end device_archs = if sdk_version < '5.0' ['armv6', 'armv7'] elsif sdk_version >= '7.0' ['armv7', 'armv7s', 'arm64'] elsif sdk_version >= '6.0' ['armv7', 'armv7s'] else ['armv7'] end sim_archs = if sdk_version >= '7.0' ['i386', 'x86_64'] else ['i386'] end [['iPhoneOS', *device_archs], ['iPhoneSimulator', *sim_archs]].each do |platform, *archs| obj = "ios/#{sdk_version}/#{platform}/#{framework}_stubs.o" next if File.exist?(obj) platform_dev = "#{XCODE_PLATFORMS_DIR}/#{platform}.platform/Developer" cflags = "-isysroot #{platform_dev}/SDKs/#{platform}#{sdk_version}.sdk " cflags << archs.map { |a| "-arch #{a}" }.join(' ') cflags << " -miphoneos-version-min=#{sdk_version} -Wall -Werror -O3 -fobjc-abi-version=2 -fobjc-legacy-dispatch" sh "#{XCODE_PLATFORMS_DIR}/../Toolchains/XcodeDefault.xctoolchain/usr/bin/clang #{cflags} #{code} -c -o #{obj}" end end end end namespace :UIAutomation do desc 'Dump the headers of the private iOS UIAutomation frameworks' task :dump_headers do if `which class-dump`.empty? $stderr.puts "[!] This requires the `class-dump` tool. Install it with: $ brew install class-dump." exit 1 end IOS_SDK_VERSIONS.each do |sdk_version| framework_path = "#{XCODE_PLATFORMS_DIR}/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator#{sdk_version}.sdk/Developer/Library/PrivateFrameworks/UIAutomation.framework" destination = File.join('UIAutomation', sdk_version) next if File.exist?(destination) # Use the 386 slice here because it's easier to differentiate between # CGFloat and NSTimeInterval. Otherwise a `double` arg is too ambigous. sh "class-dump --arch i386 -H -o #{destination} '#{framework_path}'" Dir.glob("UIAutomation/#{sdk_version}/*.h").each do |header| content = File.read(header) if File.basename(header) == 'CDStructures.h' # Remove definitions of structs such as `CGRect`. content.sub!(/#pragma mark Named Structures.+?#pragma/m, '#pragma') else # Use definitions such as `CGRect` from canonical headers. content = "#import \n" << content # Replace `struct CGRect` with just `CGRect`. content.gsub!(/struct (CGAffineTransform|CGPoint|CGSize|CGRect)/) { $1 } content.gsub!(/(duration):\(double\)/i) { "#{$1}:(NSTimeInterval)" } content.gsub!(/\(((float|int|unsigned int)(\s*\*)?)\)/) do type = case $2 when 'float' 'CGFloat' when 'int' 'NSInteger' when 'unsigned int' 'NSUInteger' end "(#{type}#{$3})" end end File.open(header, 'w') { |f| f << content } end end end desc 'Generate BridgeSupport from dumped headers' task :generate_bridgesupport_files => :dump_headers do IOS_SDK_VERSIONS.each do |sdk_version| destination = "ios/#{sdk_version}/BridgeSupport/UIAutomation.bridgesupport" next if File.exist?(destination) platform_dev_path = "#{XCODE_PLATFORMS_DIR}/iPhoneSimulator.platform/Developer" sdk_path = "#{platform_dev_path}/SDKs/iPhoneSimulator#{sdk_version}.sdk" a = sdk_version.scan(/(\d+)\.(\d+)/)[0] sdk_version_headers = ((a[0].to_i * 10000) + (a[1].to_i * 100)).to_s extra_flags = (sdk_version >= '7.0') ? '--64-bit' : '--no-64-bit' Dir.chdir "UIAutomation/#{sdk_version}" do sh "/usr/bin/gen_bridge_metadata --format complete #{extra_flags} --cflags \"-isysroot #{sdk_path} -miphoneos-version-min=#{sdk_version} -DTARGET_OS_IPHONE -D__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__=#{sdk_version_headers} -I.\" *.h > ../../#{destination}" end end end end task :clean do IOS_SDK_VERSIONS.each { |path| rm_rf('ios/' + path) } OSX_SDK_VERSIONS.each { |path| rm_rf('osx/' + path) } ANDROID_API_VERSIONS.each { |path| rm_rf('android/' + path) } Dir.glob(["ios/*.dylib", "osx/*.dylib"]) { |path| rm_rf(path) } rm_rf 'UIAutomation' end # TODO - merge to main task task :android do ANDROID_API_VERSIONS.each do |api_vers| # Copy runtime library and kernel file ANDROID_ARCHS.each do |arch| dest_lib_path = "android/#{api_vers}/#{arch}" mkdir_p dest_lib_path vm_objs_dir = "../vm/android-#{api_vers}-#{arch}.objs" cp "#{vm_objs_dir}/librubymotion-static.a", dest_lib_path cp "#{vm_objs_dir}/kernel-#{arch}.bc", dest_lib_path end # Copy runtime jar. cp "../vm/android-#{api_vers}-classes/rubymotion.jar", "android/#{api_vers}" # Generate the android.bridgesupport file. jar_path = File.join(ANDROID_SDK, 'platforms', "android-#{api_vers}", 'android.jar') bs_path = "android/#{api_vers}/BridgeSupport/android.bridgesupport" if !File.exist?(bs_path) or File.mtime(jar_path) > File.mtime(bs_path) mkdir_p File.dirname(bs_path) sh "/usr/bin/ruby ../bin/android/gen_bridge_metadata \"#{jar_path}\" \"#{bs_path}\"" end end end