-
Notifications
You must be signed in to change notification settings - Fork 464
/
Copy pathuuid_generator.rb
132 lines (115 loc) · 4.73 KB
/
uuid_generator.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
module Xcodeproj
class Project
class UUIDGenerator
require 'digest'
def initialize(projects)
@projects = Array(projects)
@paths_by_object = {}
end
def generate!
generate_all_paths_by_objects(@projects)
new_objects_by_project = Hash[@projects.map do |project|
[project, switch_uuids(project)]
end]
all_new_objects_by_project = new_objects_by_project.values.flat_map(&:values)
all_objects_by_uuid = @projects.map(&:objects_by_uuid).inject(:merge)
all_objects = @projects.flat_map(&:objects)
verify_no_duplicates!(all_objects, all_new_objects_by_project)
@projects.each { |project| fixup_uuid_references(project, all_objects_by_uuid) }
new_objects_by_project.each do |project, new_objects_by_uuid|
project.instance_variable_set(:@generated_uuids, project.instance_variable_get(:@available_uuids))
project.instance_variable_set(:@objects_by_uuid, new_objects_by_uuid)
end
end
private
UUID_ATTRIBUTES = [:remote_global_id_string, :container_portal, :target_proxy].freeze
def verify_no_duplicates!(all_objects, all_new_objects)
duplicates = all_objects - all_new_objects
UserInterface.warn "[Xcodeproj] Generated duplicate UUIDs:\n\n" <<
duplicates.map { |d| "#{d.isa} -- #{@paths_by_object[d]}" }.join("\n") unless duplicates.empty?
end
def fixup_uuid_references(target_project, all_objects_by_uuid)
fixup = ->(object, attr) do
if object.respond_to?(attr) && link = all_objects_by_uuid[object.send(attr)]
object.send(:"#{attr}=", link.uuid)
end
end
target_project.objects.each do |object|
UUID_ATTRIBUTES.each do |attr|
fixup[object, attr]
end
end
if (project_attributes = target_project.root_object.attributes) && project_attributes['TargetAttributes']
project_attributes['TargetAttributes'] = Hash[project_attributes['TargetAttributes'].map do |target_uuid, attributes|
if test_target_id = attributes['TestTargetID']
attributes = attributes.merge('TestTargetID' => all_objects_by_uuid[test_target_id].uuid)
end
if target_object = all_objects_by_uuid[target_uuid]
target_uuid = target_object.uuid
end
[target_uuid, attributes]
end]
end
end
def generate_all_paths_by_objects(projects)
projects.each { |project| generate_paths(project.root_object, project.path.basename.to_s) }
end
def generate_paths(object, path = '')
existing = @paths_by_object[object] || path
return existing if @paths_by_object.key?(object)
@paths_by_object[object] = path.size > existing.size ? path : existing
object.to_one_attributes.each do |attrb|
obj = attrb.get_value(object)
generate_paths(obj, path + '/' << attrb.plist_name) if obj
end
object.to_many_attributes.each do |attrb|
attrb.get_value(object).each do |o|
generate_paths(o, path + '/' << attrb.plist_name << "/#{path_component_for_object(o)}")
end
end
object.references_by_keys_attributes.each do |attrb|
attrb.get_value(object).each do |dictionary|
dictionary.each do |key, value|
generate_paths(value, path + '/' << attrb.plist_name << "/k:#{key}/#{path_component_for_object(value)}")
end
end
end
end
def switch_uuids(project)
project.mark_dirty!
project.objects.each_with_object({}) do |object, hash|
next unless path = @paths_by_object[object]
uuid = uuid_for_path(path)
object.instance_variable_set(:@uuid, uuid)
hash[uuid] = object
end
end
def uuid_for_path(path)
Digest::MD5.hexdigest(path).upcase
end
def path_component_for_object(object)
@path_component_for_object ||= Hash.new do |cache, key|
component = tree_hash_to_path(key.to_tree_hash)
component << key.hierarchy_path.to_s if key.respond_to?(:hierarchy_path)
cache[key] = component
end
@path_component_for_object[object]
end
def tree_hash_to_path(object, depth = 4)
return '|' if depth.zero?
case object
when Hash
object.sort.each_with_object('') do |(key, value), string|
string << key << ':' << tree_hash_to_path(value, depth - 1) << ','
end
when Array
object.map do |value|
tree_hash_to_path(value, depth - 1)
end.join(',')
else
object.to_s
end
end
end
end
end