Skip to content

Commit

Permalink
Fixup UUID generation.
Browse files Browse the repository at this point in the history
Support the ability to pass in a list of projects that can be used to generate deterministic UUIDs, and also initially pass into UUID generation the project basename such that we have unique UUIDs across all projects.
  • Loading branch information
sebastianv1 committed Nov 29, 2018
1 parent acfb037 commit 5b0ba90
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 24 deletions.
6 changes: 5 additions & 1 deletion lib/xcodeproj/project.rb
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,11 @@ def dirty?
# @return [void]
#
def predictabilize_uuids
UUIDGenerator.new(self).generate!
UUIDGenerator.new([self]).generate!
end

def self.predictabilize_uuids(projects)
UUIDGenerator.new(projects).generate!
end

public
Expand Down
50 changes: 31 additions & 19 deletions lib/xcodeproj/project/uuid_generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,55 @@ class Project
class UUIDGenerator
require 'digest'

def initialize(project)
@project = project
@new_objects_by_uuid = {}
def initialize(projects)
@projects = projects
@paths_by_object = {}
end

def generate!
all_objects = @project.objects
generate_paths(@project.root_object)
switch_uuids(all_objects)
verify_no_duplicates!(all_objects)
fixup_uuid_references
@project.instance_variable_set(:@generated_uuids, @project.instance_variable_get(:@available_uuids))
@project.instance_variable_set(:@objects_by_uuid, @new_objects_by_uuid)
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.map { |new_objects_by_uuid| new_objects_by_uuid.values }.flatten
all_objects_by_uuid = @projects.map { |project| project.objects_by_uuid }.inject(:merge)
all_objects = @projects.map { |project| project.objects }.flatten
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)
duplicates = all_objects - @new_objects_by_uuid.values
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
def fixup_uuid_references(target_project, all_objects_by_uuid)
fixup = ->(object, attr) do
if object.respond_to?(attr) && link = @project.objects_by_uuid[object.send(attr)]
if object.respond_to?(attr) && link = all_objects_by_uuid[object.send(attr)]
object.send(:"#{attr}=", link.uuid)
end
end
@project.objects.each do |object|
target_project.objects.each do |object|
UUID_ATTRIBUTES.each do |attr|
fixup[object, attr]
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)
Expand All @@ -67,14 +77,16 @@ def generate_paths(object, path = '')
end
end

def switch_uuids(objects)
@project.mark_dirty!
objects.each do |object|
def switch_uuids(project)
new_objects_by_uuid = {}
project.mark_dirty!
project.objects.each do |object|
next unless path = @paths_by_object[object]
uuid = uuid_for_path(path)
object.instance_variable_set(:@uuid, uuid)
@new_objects_by_uuid[uuid] = object
new_objects_by_uuid[uuid] = object
end
new_objects_by_uuid
end

def uuid_for_path(path)
Expand Down
24 changes: 20 additions & 4 deletions spec/project_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -480,20 +480,36 @@ module ProjectSpecs
project
end

it 'not have the same UUIDS' do
create_project[0].uuids.sort.should != create_project[1].uuids.sort
end

it 'have the same UUIDS' do
create_project[0].uuids.sort.should == create_project[1].uuids.sort
create_project[0].uuids.sort.should == create_project[0].uuids.sort
end

it 'always has the same root object UUID, for the same path' do
project = Xcodeproj::Project.new('path1.xcodeproj')
project.add_build_configuration('Config', :debug)
project.predictabilize_uuids
project.root_object.uuid.should == 'BB3CBEC84BD00C24CC882BA09F25FC35'

project = Xcodeproj::Project.new('path1.xcodeproj')
project.add_build_configuration('Config', :release)
project.predictabilize_uuids
project.root_object.uuid.should == 'BB3CBEC84BD00C24CC882BA09F25FC35'
end

it 'always has the same root object UUID, even for different paths' do
it 'has different root object UUID, for different paths' do
project = Xcodeproj::Project.new('path1.xcodeproj')
project.add_build_configuration('Config', :debug)
project.predictabilize_uuids
project.root_object.uuid.should == 'D41D8CD98F00B204E9800998ECF8427E'
project.root_object.uuid.should == 'BB3CBEC84BD00C24CC882BA09F25FC35'

project = Xcodeproj::Project.new('path2.xcodeproj')
project.add_build_configuration('Config', :release)
project.predictabilize_uuids
project.root_object.uuid.should == 'D41D8CD98F00B204E9800998ECF8427E'
project.root_object.uuid.should == '58C5CB80A8C53842A0F017EE22887DF0'
end

Pathname.glob("#{fixture_path}/**/*.xcodeproj").each do |path|
Expand Down

0 comments on commit 5b0ba90

Please sign in to comment.