Created
January 14, 2019 07:22
-
-
Save breezeight/26c2d8d5b005bba6dd935f6fa01705fe to your computer and use it in GitHub Desktop.
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
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