Skip to content

Instantly share code, notes, and snippets.

@breezeight
Created January 14, 2019 07:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save breezeight/26c2d8d5b005bba6dd935f6fa01705fe to your computer and use it in GitHub Desktop.
Save breezeight/26c2d8d5b005bba6dd935f6fa01705fe to your computer and use it in GitHub Desktop.
require 'xcodeproj'
#require 'xcodeproj/project/object/build_configuration'
require 'find'
require 'yaml'
require 'pry'
class XCodeProject
def initialize(path)
unless File.exists?(path)
raise Errno::ENOENT, "#{path} does not exist."
end
@path = path
@_name = File.basename(path, '.xcodeproj')
@proj = Xcodeproj::Project.open(path)
@build_config_names = @proj.build_configurations.collect(&:name).to_set
@debug_bc = build_config_named 'Debug'
@release_bc = build_config_named 'Release'
end
def project_name
@_name
end
def deep_dup(object)
case object
when Hash
new_hash = {}
object.each do |key, value|
new_hash[key] = deep_dup(value)
end
new_hash
when Array
object.map { |value| deep_dup(value) }
else
object.dup
end
end
def build_configurations
@proj.build_configurations
end
def mirror_settings(from_bc, to_bc)
to_bc.build_settings = deep_dup(from_bc.build_settings)
puts from_bc.base_configuration_reference
to_bc.base_configuration_reference = from_bc.base_configuration_reference
end
def duplicate_debug_as(name)
bc = @proj.add_build_configuration(name, :debug)
mirror_settings(@debug_bc, bc)
@proj.targets.each do |target|
from_target_bc = target.build_configurations.find { |bc| bc.name == "Debug" }
target_bc = target.add_build_configuration(name, :debug)
mirror_settings(from_target_bc, target_bc)
end
end
def duplicate_release_as(name)
bc = @proj.add_build_configuration(name, :release)
mirror_settings(@release_bc, bc)
@proj.targets.each do |target|
from_target_bc = target.build_configurations.find { |bc| bc.name == "Release" }
target_bc = target.add_build_configuration(name, :release)
mirror_settings(from_target_bc, target_bc)
end
end
def duplicate_build_configuration(target_or_proj, from_bc, to_bc_name)
build_configuration = @proj.new(Xcodeproj::Project::Object::XCBuildConfiguration)
build_configuration.name = to_bc_name
mirror_settings(from_bc,build_configuration)
target_or_proj.build_configuration_list.build_configurations << build_configuration
build_configuration
end
def find_or_create_build_config(target_or_proj, name, kind)
base_bc_name = if kind == :debug
"Debug"
else
"Release"
end
base_bc = target_or_proj.build_configuration_list[base_bc_name]
puts "Checking '#{target_or_proj.respond_to?(:name) ? target_or_proj.name : "Project"}'"
if bc = target_or_proj.build_configuration_list[name]
puts " Configuration '#{name}' already exists."
puts " Ensuring settings are properly mirrored."
mirror_settings(base_bc, bc)
else
puts " Configuration '#{name}' for Not Found."
puts " Creating new one based off of #{base_bc.name}."
duplicate_build_configuration(target_or_proj, base_bc, name)
end
end
def ensure_build_config(name, kind)
find_or_create_build_config @proj, name, kind
@proj.targets.each do |target|
find_or_create_build_config target, name, kind
end
end
def build_config_named(name)
@proj.build_configurations.find { |bc| bc.name == name }
end
def all_build_phases
@proj.targets.map{|t|t.build_phases}.flatten
end
def has_config_named(name)
@build_config_names.include?(name)
end
def find_run_script_phase
all_build_phases.find do |bp|
bp.is_a?(Xcodeproj::Project::Object::PBXShellScriptBuildPhase) && yield(bp)
end
end
def save
@proj.save
end
end
class BuildConfigSynchronizer
def initialize(search_path=File.join(File.dirname(__FILE__), '../node_modules'))
@search_path = search_path
load_config
find_and_fix_projects
end
def load_config(config_path=File.join(File.dirname(__FILE__), './build-config-mappings.yml'))
puts "Loading mappings from: #{config_path}..."
unless File.exists?(config_path)
raise Errno::ENOENT, "You need to make a yaml file."
end
@mappings = YAML.load_file(config_path)
puts "Config loaded."
all_names = @mappings.map{|k,n|n}.flatten
max_name_len = all_names.map{|n|n.length}.max
@mappings.each do |kind,names|
names.each do |name|
puts " #{name.ljust(max_name_len + 1)}=> #{kind}"
end
puts "\n"
end
puts "\n"
end
def fix_project(project)
puts "Fixing #{project.project_name}..."
puts " Ensuring all build configs are properly mapped"
@mappings.each do |kind,names|
names.each do |name|
project.ensure_build_config(name, kind)
end
end
project.save
end
def fix_react_project(project)
puts "Fixing #{project.project_name}..."
puts " Ensuring all build configs are properly mapped"
@mappings.each do |kind,names|
names.each do |name|
project.ensure_build_config(name, kind)
end
end
puts " Making sure run scripts have correct conditionals."
bp = project.find_run_script_phase do |rs_phase|
rs_phase.shell_script.include?('"Debug"')
end
if bp
puts " Run script needs updating."
ss_text = bp.shell_script.split("\n").map{|s| " #{s}"}.join("\n")
puts " Found:\n#{ss_text}\n"
bp.shell_script = bp.shell_script.sub('"Debug"', '*Debug')
ss_text = bp.shell_script.split("\n").map{|s| " #{s}"}.join("\n")
puts " Replaced with:\n#{ss_text}\n"
else
puts " Run script seems in order."
end
puts "Finished fixing #{project.project_name}.\n\n"
project.save
end
def fix_react_websocket_project(project)
puts "Fixing #{project.project_name}..."
puts " Ensuring all build configs are properly mapped"
@mappings.each do |kind,names|
names.each do |name|
project.ensure_build_config(name, kind)
# For some reason, these settings aren't duplicated.
# buildSettings = {
# EXECUTABLE_PREFIX = lib;
# GCC_TREAT_WARNINGS_AS_ERRORS = NO;
# HEADER_SEARCH_PATHS = (
# "$(inherited)",
# /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
# "$(SRCROOT)/../../React/**",
# "$(SRCROOT)/../SRWebSocket/",
# );
# OTHER_LDFLAGS = "-ObjC";
# PRODUCT_NAME = "$(TARGET_NAME)";
# SKIP_INSTALL = YES;
# };
# name = "Production Debug";
#
# Manually copy them over.
bc = project.build_config_named name
bc.build_settings['EXECUTABLE_PREFIX'] = 'lib'
bc.build_settings['GCC_TREAT_WARNINGS_AS_ERRORS'] = 'NO'
bc.build_settings['HEADER_SEARCH_PATHS'] = [
'"$(inherited)"',
'/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include',
'"$(SRCROOT)/../../React/**"',
'"$(SRCROOT)/../SRWebSocket/"',
]
bc.build_settings['OTHER_LDFLAGS'] = '"-ObjC"'
bc.build_settings['PRODUCT_NAME'] = '"$(TARGET_NAME)"'
bc.build_settings['SKIP_INSTALL'] = "YES"
end
end
puts "Finished fixing #{project.project_name}.\n\n"
project.save
end
def fix_crashlytics_project(project)
puts "Fixing #{project.project_name}..."
new_search_path = '$(SRCROOT)/../../../ios/Pods/Headers/Public/Crashlytics'
project.build_configurations.each do |bc|
search_paths = bc.build_settings['HEADER_SEARCH_PATHS']
unless search_paths.include?(new_search_path)
search_paths << new_search_path
bc.build_settings['HEADER_SEARCH_PATHS'] = search_paths
end
end
puts "Finished fixing #{project.project_name}.\n\n"
project.save
end
def find_and_fix_projects
puts "Searching #{@search_path} for relevant xcodeproj's...\n\n"
found = 0
need_to_find = 3
Find.find(@search_path) do |e|
if e =~ /\AReact\.xcodeproj/
puts "Found React's main xcodeproj file. #{e}"
fix_react_project(XCodeProject.new(e))
found += 1
if found == need_to_find
return
end
Find.prune
elsif e =~ /RCTWebSocket\.xcodeproj\Z/
puts "Found React's WebSocket xcodeproj file. #{e}"
fix_react_websocket_project(XCodeProject.new(e))
found += 1
if found == need_to_find
return
end
Find.prune
elsif e =~ /SMXCrashlytics\.xcodeproj\Z/
puts "Found SMXCrashlytics's xcodeproj file. #{e}"
fix_crashlytics_project(XCodeProject.new(e))
found += 1
if found == need_to_find
return
end
Find.prune
elsif e =~ /\.xcodeproj\Z/
puts "FOUND xcodeproj file: #{e}"
fix_project(XCodeProject.new(e))
Find.prune
end
end
Find.find("ios/Pods") do |e|
if e =~ /\.xcodeproj\Z/
puts "FOUND xcodeproj file: #{e}"
fix_project(XCodeProject.new(e))
Find.prune
end
end
end
end
BuildConfigSynchronizer.new
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment