|
#!/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." |