Created
November 27, 2013 04:59
PostprocessBuildPlayer for Unity, Written in Ruby.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
# | |
# PostprocessBuildPlayer version 2 | |
# Tested on Ruby 1.8.7, Gem 1.3.6, and xcodeproj 0.4.1 | |
# Created by akisute (http://akisute.com) | |
# Licensed under The MIT License: http://opensource.org/licenses/mit-license.php | |
# | |
require 'rubygems' | |
# least require version, doesn't work in 0.3.X or less | |
# possibly work on higher version than 0.4.X, but not tested | |
gem "xcodeproj", "~>0.4.0" | |
require 'xcodeproj' | |
require 'pathname' | |
require 'fileutils' | |
# | |
# Define utility functions | |
# | |
def proj_add_system_framework(proj, name) # workaround for 0.4.0 bug of Project#add_system_framework | |
path = "System/Library/Frameworks/#{name}.framework" | |
framework_ref = proj.frameworks_group.new_file(path) | |
framework_ref.name = "#{name}.framework" | |
framework_ref.source_tree = 'SDKROOT' | |
framework_ref | |
end | |
def proj_add_dylib(proj, name) # added new function, because there's no way we can add dylibs in 0.4.0 | |
path = "usr/lib/#{name}.dylib" | |
framework_ref = proj.frameworks_group.new_file(path) | |
framework_ref.name = "#{name}.dylib" | |
framework_ref.source_tree = 'SDKROOT' | |
framework_ref | |
end | |
def add_system_frameworks_to_project(proj, framework_names, option=:required) | |
proj.targets.each do |target| | |
# if target.name == "Unity-iPhone-simulator" then | |
# next | |
# end | |
framework_names.each { |framework_name| | |
framework = proj_add_system_framework(proj, framework_name) # workaround for 0.4.0 bug of Project#add_system_framework | |
phase = target.build_phases.find { |phase| phase.is_a?(Xcodeproj::Project::PBXFrameworksBuildPhase) } | |
ref = phase.add_file_reference(framework) | |
if option == :optional then | |
ref.settings = { "ATTRIBUTES" => ["Weak"] } | |
end | |
# ref = Xcodeproj::Project::PBXBuildFile.new(proj, nil) | |
# ref.file_ref = framework | |
# if option == :optional then | |
# ref.settings = { "ATTRIBUTES" => ["Weak"] } | |
# end | |
# phase = target.build_phases.find { |phase| phase.is_a?(Xcodeproj::Project::PBXFrameworksBuildPhase) } | |
# phase.files << ref | |
puts "Added system framework: " + framework_name + " as " + option.id2name | |
} | |
end | |
end | |
def add_dylibs_to_project(proj, dylib_names, option=:required) | |
proj.targets.each do |target| | |
# if target.name == "Unity-iPhone-simulator" then | |
# next | |
# end | |
dylib_names.each { |dylib_name| | |
dylib = proj_add_dylib(proj, dylib_name) | |
phase = target.build_phases.find { |phase| phase.is_a?(Xcodeproj::Project::PBXFrameworksBuildPhase) } | |
ref = phase.add_file_reference(dylib) | |
if option == :optional then | |
ref.settings = { "ATTRIBUTES" => ["Weak"] } | |
end | |
puts "Added dylib: " + dylib_name + " as " + option.id2name | |
} | |
end | |
end | |
def new_variant_group_for_group(group, name, sub_group_path=nil) | |
variant_group = group.project.new(Xcodeproj::Project::PBXVariantGroup) | |
variant_group.name = name | |
target = group.find_subpath(sub_group_path, true) | |
target.children << variant_group | |
variant_group | |
end | |
def add_infoplist_strings_to_project(proj, buildpath) | |
system("mkdir #{buildpath}/en.lproj") | |
system("touch #{buildpath}/en.lproj/InfoPlist.strings") | |
system("mkdir #{buildpath}/ja.lproj") | |
system("touch #{buildpath}/ja.lproj/InfoPlist.strings") | |
proj.targets.each do |target| | |
# if target.name == "Unity-iPhone-simulator" then | |
# next | |
# end | |
infoplist_ref = new_variant_group_for_group(proj.main_group, "InfoPlist.strings") | |
en_ref = infoplist_ref.new_file("en.lproj/InfoPlist.strings") | |
en_ref.name = "en" | |
ja_ref = infoplist_ref.new_file("ja.lproj/InfoPlist.strings") | |
ja_ref.name = "ja" | |
buildfile_ref = Xcodeproj::Project::PBXBuildFile.new(proj, nil) | |
buildfile_ref.file_ref = infoplist_ref | |
phase = target.build_phases.find { |phase| phase.is_a?(Xcodeproj::Project::PBXResourcesBuildPhase) } | |
phase.files << buildfile_ref | |
puts "Added InfoPlist.strings" | |
end | |
end | |
def set_header_search_paths(proj) | |
proj.targets.each do |target| | |
# if target.name == "Unity-iPhone-simulator" then | |
# next | |
# end | |
target.build_configuration_list.build_configurations.each do |build_configuration| | |
header_search_paths = build_configuration.build_settings["LIBRARY_SEARCH_PATHS"] | |
build_configuration.build_settings["HEADER_SEARCH_PATHS"] = header_search_paths | |
puts "Added HEADER_SEARCH_PATHS: #{header_search_paths} to build configuration #{build_configuration.name}" | |
end | |
end | |
end | |
def set_launch_image(proj, unity_project_root_path, buildpath, option) | |
imagepath = "" | |
ext = "" | |
case option | |
when :png | |
imagepath = unity_project_root_path + "/Assets/Images" | |
ext = option.id2name | |
when :jpg, :jpeg | |
imagepath = unity_project_root_path + "/Assets/Images/jpg" | |
ext = option.id2name | |
system("/usr/libexec/PlistBuddy -c 'Add :UILaunchImageFile string Default.#{ext}' #{buildpath}/Info.plist") | |
proj.targets.each do |target| | |
# if target.name == "Unity-iPhone-simulator" then | |
# next | |
# end | |
resource_build_phase = target.build_phases.find { |phase| phase.is_a?(Xcodeproj::Project::PBXResourcesBuildPhase) } | |
resource_build_phase.files.each do |file| | |
re = Regexp.new("^(Default.*?)\.png") | |
if re === file.file_ref.name then | |
file.file_ref.name = file.file_ref.name.gsub(re, "\\1.#{ext}") | |
end | |
if re === file.file_ref.path then | |
file.file_ref.path = file.file_ref.path.gsub(re, "\\1.#{ext}") | |
file.file_ref.last_known_file_type = "image.jpeg" | |
end | |
end | |
delete_files = [] | |
sources_build_phase = target.build_phases.find { |phase| phase.is_a?(Xcodeproj::Project::PBXSourcesBuildPhase) } | |
sources_build_phase.files.each do |file| | |
re = Regexp.new("^(Default.*?)\.png") | |
if re === file.file_ref.name then | |
file.file_ref.name = file.file_ref.name.gsub(re, "\\1.#{ext}") | |
end | |
if re === file.file_ref.path then | |
file.file_ref.path = file.file_ref.path.gsub(re, "\\1.#{ext}") | |
file.file_ref.last_known_file_type = "image.jpeg" | |
resource_build_phase.files << file | |
delete_files << file | |
end | |
end | |
delete_files.each do |file| | |
sources_build_phase.files.delete(file) | |
end | |
end | |
end | |
FileUtils.cp("#{imagepath}/Default.#{ext}", "#{buildpath}/Default.#{ext}") | |
FileUtils.cp("#{imagepath}/Default@2x.#{ext}", "#{buildpath}/Default@2x.#{ext}") | |
FileUtils.cp("#{imagepath}/Default-568h@2x.#{ext}", "#{buildpath}/Default-568h@2x.#{ext}") | |
FileUtils.cp("#{imagepath}/Default-Landscape.#{ext}", "#{buildpath}/Default-Landscape.#{ext}") | |
FileUtils.cp("#{imagepath}/Default-Landscape@2x.#{ext}", "#{buildpath}/Default-Landscape@2x.#{ext}") | |
FileUtils.cp("#{imagepath}/Default-Portrait.#{ext}", "#{buildpath}/Default-Portrait.#{ext}") | |
FileUtils.cp("#{imagepath}/Default-Portrait@2x.#{ext}", "#{buildpath}/Default-Portrait@2x.#{ext}") | |
puts "Added Default images (#{ext}) from #{imagepath}" | |
end | |
# | |
# Define build directory path | |
# -> Will be suppried as argv if run by Unity | |
# -> Else, assume unity_project_root_path/build is a build directory | |
# | |
unity_project_root_path = File.expand_path(File.dirname($0)) + "/../.." | |
buildpath = (ARGV[0]) ? ARGV[0] : unity_project_root_path + "/build" | |
puts "PostprocessBuildPlayer running on build directory: " + buildpath | |
projpath = buildpath + "/Unity-iPhone.xcodeproj" | |
proj = Xcodeproj::Project.new(projpath) | |
############################################################################### | |
# Example Usages | |
############################################################################### | |
# | |
# Add System frameworks and dynamic libralies to build | |
# | |
add_system_frameworks_to_project(proj, ["StoreKit", "Security", "CoreText", "MessageUI"], :required) | |
add_system_frameworks_to_project(proj, ["Twitter", "Social"], :optional) | |
add_dylibs_to_project(proj, ["libsqlite3"], :required) | |
# | |
# Set HEADER_SEARCH_PATHS | |
# | |
set_header_search_paths(proj) | |
# | |
# Add UIRequiredDeviceCapabilities to Info.plist | |
# | |
system("/usr/libexec/PlistBuddy -c 'Add :UIRequiredDeviceCapabilities: string gyroscope' #{buildpath}/Info.plist") | |
# | |
# Update lproj files | |
# | |
add_infoplist_strings_to_project(proj, buildpath) | |
# | |
# Set Launch Image (Default.png) | |
# | |
launch_image_ext = :jpg | |
set_launch_image(proj, unity_project_root_path, buildpath, launch_image_ext) | |
# | |
# Save xcodeproj | |
# | |
proj.save_as(projpath) | |
# | |
# Rewrite main.mm | |
# Add uncaught exception handler for better debugging | |
# Ignores SIGPIPE which rises when application enters background while System.Net.Socket.Socket is open (There's no other way to shut this) | |
# | |
f_main_mm = open(buildpath + "/Classes/main.mm", "r") | |
text_main_mm = f_main_mm.read | |
f_main_mm.close | |
f_main_mm = open(buildpath + "/Classes/main.mm", "w") | |
text_main_mm = text_main_mm.sub( | |
'int main(int argc, char *argv[])', | |
'void uncaughtExceptionHandler(NSException *exception); | |
void uncaughtExceptionHandler(NSException *exception) | |
{ | |
NSLog(@"%@", exception); | |
NSLog(@"Stack Trace: %@", [exception callStackSymbols]); | |
} | |
int main/*substituted by PostprocessBuildPlayer*/(int argc, char *argv[])' | |
); | |
text_main_mm = text_main_mm.sub( | |
'UnityParseCommandLine(argc, argv);', | |
'signal(SIGPIPE, SIG_IGN); | |
NSSetUncaughtExceptionHandler(&uncaughtExceptionHandler); | |
UnityParseCommandLine/*substituted by PostprocessBuildPlayer*/(argc, argv);' | |
); | |
f_main_mm.write(text_main_mm) | |
f_main_mm.close | |
puts "Updated: main.mm" | |
# | |
# Rewrite AppController.mm | |
# Fixes UIScrollView problems by rewriting run loop modes | |
# https://github.com/keijiro/unity-ios-textview | |
# | |
# Also fixes problems when you use jpg images as a launch image | |
# | |
f_app_controller_mm = open(buildpath + "/Classes/AppController.mm", "r") | |
text_app_controller_mm = f_app_controller_mm.read | |
f_app_controller_mm.close | |
f_app_controller_mm = open(buildpath + "/Classes/AppController.mm", "w") | |
text_app_controller_mm = text_app_controller_mm.sub( | |
'while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, kInputProcessingTime, TRUE) == kCFRunLoopRunHandledSource)', | |
'while (CFRunLoopRunInMode(CFStringRef(UITrackingRunLoopMode), kInputProcessingTime, YES) == kCFRunLoopRunHandledSource)' | |
); | |
text_app_controller_mm = text_app_controller_mm.sub( | |
'[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];', | |
'[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];' | |
); | |
text_app_controller_mm = text_app_controller_mm.sub( | |
'return [NSString stringWithFormat:@"Default%s%s", orientSuffix, szSuffix];', | |
'return [NSString stringWithFormat:@"Default%s%s.' + launch_image_ext.id2name + '", orientSuffix, szSuffix];' | |
); | |
f_app_controller_mm.write(text_app_controller_mm) | |
f_app_controller_mm.close | |
puts "Updated: AppController.mm" | |
puts "PostprocessBuildPlayer completed." |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment