Class: ZergXcode::Plugins::Import

Inherits:
Object
  • Object
show all
Includes:
Objects
Defined in:
lib/zerg_xcode/plugins/import.rb

Overview

Imports the contents of an Xcode project into another Xcode project.

Instance Method Summary collapse

Instance Method Details

#bin_mappings(mappings, source) ⇒ Object

Bins merge mappings for a project into mappings to be merged and mappings to be overwritten.



133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/zerg_xcode/plugins/import.rb', line 133

def bin_mappings(mappings, source)
  merge_set = Set.new
  overwrite_set = Set.new

  # the project's top-level attributes are always merged
  source.attrs.each do |attr|
    merge_set << source[attr] if mappings[source[attr]]
  end
      
  mappings.each do |source_object, target_object|
    next if source_object == target_object
    next if merge_set.include? source_object
    
    if source_object.kind_of?(PBXGroup) && source_object['path'].nil?
      merge_set << source_object
    elsif source_object.kind_of? XCConfigurationList
      merge_set << source_object
    else
      overwrite_set << source_object
    end
  end
  
  overwrite_set.delete source
  { :merge => merge_set, :overwrite => overwrite_set }
end

#clean_join(root, path) ⇒ Object



127
128
129
# File 'lib/zerg_xcode/plugins/import.rb', line 127

def clean_join(root, path)
  Pathname.new(File.join(root, path)).cleanpath.to_s
end

#compute_copies(source_root, source_paths, target_root) ⇒ Object

Computes the file copy operations in a merge.

Copies all the files from the source project assuming they received the same path in the target project. The assumption is correct if source was imported into target.



120
121
122
123
124
125
# File 'lib/zerg_xcode/plugins/import.rb', line 120

def compute_copies(source_root, source_paths, target_root)
  source_paths.select { |path| path[0, 2] == './' }.map do |path|
    { :op => :copy, :from => clean_join(source_root, path),
      :to => clean_join(target_root, path) }
  end
end

#compute_deletes(root, old_paths, new_paths) ⇒ Object

Computes the file delete operations in a merge.

Deletes all the files that aren’t in the target project anymore.



108
109
110
111
112
113
# File 'lib/zerg_xcode/plugins/import.rb', line 108

def compute_deletes(root, old_paths, new_paths)
  new_path_set = Set.new(new_paths)
  old_paths.select { |path| path[0, 2] == './' }.
            reject { |path| new_path_set.include? path }.
            map { |path| { :op => :delete, :path => clean_join(root, path) } }
end

#cross_reference(source, target, strict = false, mappings = {}) ⇒ Object

Cross-references the objects in two object graphs that are to be merged. Returns a Hash associating objects in both the source and the target object graphs with the target objects that represent the same entities. Setting strict to true returns fewer matches, and should be used for entangling projects. Setting strict to false should work better for merges.



236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/zerg_xcode/plugins/import.rb', line 236

def cross_reference(source, target, strict = false, mappings = {})
  if source.class != target.class
    raise "Attempting to cross-reference different kinds of objects - " +
          "#{source.class} != #{target.class}"
  end
  
  case target
  when ZergXcode::XcodeObject
    cross_op = :cross_reference_objects
  when Hash
    cross_op = :cross_reference_hashes
  when Enumerable
    cross_op = :cross_reference_enumerables
  else
    return mappings
  end
  
  self.send cross_op, source, target, strict, mappings
end

#execute_file_ops!(file_ops) ⇒ Object

Executes the given file operations.



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/zerg_xcode/plugins/import.rb', line 39

def execute_file_ops!(file_ops)
  file_ops.each do |op|
    case op[:op]
    when :delete
      FileUtils.rm_r op[:path] if File.exist? op[:path]
    when :copy
      target_dir = File.dirname op[:to]
      FileUtils.mkdir_p  target_dir unless File.exist? target_dir
      if File.exist? op[:from]
        FileUtils.cp_r op[:from], op[:to]
      else
        print "Source does not have file #{op[:from]}\n"
      end
    end
  end
end

#helpObject



18
19
20
21
22
23
24
25
26
27
# File 'lib/zerg_xcode/plugins/import.rb', line 18

def help
  {:short => 'imports the objects from a project into another project',
   :long => <<"END" }
Usage: import source_path [target_path]

Imports the objects (build targets, files) from a source project into another
target project. Useful when the source project wraps code that can be used as a
library.
END
end

#import_project!(source, target) ⇒ Object

Imports the objects of the source project into the target project.

Attempts to preserve reference integrity in the target project, as follows. If the source objects have counterparts in the target, their contents is merged into the target project’s objects.

Returns an array of file operations that need to be performed to migrate the files associated with the two projects.

The target project is modified in place.



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
# File 'lib/zerg_xcode/plugins/import.rb', line 66

def import_project!(source, target)
  old_target_paths = target.all_files.map { |file| file[:path] } 
  
  # duplicate the source, because the duplicate's object graph will be warped
  scrap_source = ZergXcode::XcodeObject.from source
  
  mappings = cross_reference scrap_source, target
  bins = bin_mappings mappings, scrap_source
  
  # special case for merging targets
  map! scrap_source, mappings
  merge! scrap_source['targets'], target['targets']
  
  # merge the object graphs
  bins[:merge].each do |object|
    map! object, mappings
    merge! object, mappings[object]
  end
  bins[:overwrite].each do |object|
    map! object, mappings
    overwrite! object, mappings[object]
  end
  
  # make sure all the mappings point in the right place 
  target.visit_once do |object, parent, key, value|
    if mappings[value]
      next mappings[value]
    else
      next true
    end
  end
  
  new_target_paths = target.all_files.map { |file| file[:path] }
  source_paths = source.all_files.map { |file| file[:path] }
  return compute_deletes(target.root_path, old_target_paths,
                         new_target_paths) +
         compute_copies(source.root_path, source_paths, target.root_path)
end

#map!(object, mappings) ⇒ Object

Modifies an object’s attributes according to the given mappings. This explores the object graph, but does not go into sub-objects.



161
162
163
164
165
166
167
168
169
# File 'lib/zerg_xcode/plugins/import.rb', line 161

def map!(object, mappings)
  object.visit_once do |object, parent, key, value|
    if mappings[value]
      parent[key] = mappings[value]
      next false
    end
    next true
  end
end

#merge!(source, target) ⇒ Object

Merges the contents of a source object into the target object.

Warning: the target will share internal objects with the source. This is intended to be used in a bigger-level opration, where the source will be thrown away afterwards.



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/zerg_xcode/plugins/import.rb', line 176

def merge!(source, target)
  if source.class != target.class
    raise "Attempting to merge-combine different kinds of objects - " +
          "#{source.class} != #{target.class}"
  end
  
  case source
  when ZergXcode::XcodeObject
    merge! source._attr_hash, target._attr_hash
  when Hash
    source.each_key do |key|
      if !target.has_key?(key)
        target[key] = source[key]          
      elsif source[key].kind_of? ZergXcode::XcodeObject
        target[key] = source[key]
      elsif source[key].kind_of?(String)
        target[key] = source[key]
      elsif !source[key].kind_of?(Enumerable)
        target[key] = source[key]
      else
        merge! source[key], target[key]
      end
    end
  when Enumerable
    target_set = Set.new(target.to_a)
    source.each do |value|
      next if target_set.include? value
      target << value
    end
  end   
end

#overwrite!(source, target) ⇒ Object

Overwrites the contents of the target object with the source object.

Warning: the target will share internal objects with the source. This is intended to be used in a bigger-level opration, where the source will be thrown away afterwards.



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
# File 'lib/zerg_xcode/plugins/import.rb', line 213

def overwrite!(source, target)
  if source.class != target.class    
    raise "Attempting to overwrite-combine different kinds of objects - " +
          "#{source.class} != #{target.class}"
  end
  
  case source
  when ZergXcode::XcodeObject
    overwrite! source._attr_hash, target._attr_hash
  when Hash
    target.clear
    target.merge! source
  when Enumerable
    target.clear
    source.each { |value| target << value }      
  end
end

#run(args) ⇒ Object



29
30
31
32
33
34
35
36
# File 'lib/zerg_xcode/plugins/import.rb', line 29

def run(args)
  source = ZergXcode.load args.shift
  target = ZergXcode.load(args.shift || '.')
  
  file_ops = import_project! source, target
  target.save!
  execute_file_ops! file_ops 
end