| #! /usr/bin/ruby |
| # |
| |
| # A script to impose order on the Xcode project file, to make merging |
| # across branches were many additional files are present, easier. |
| |
| |
| |
| |
| ## Sort the BuildFile and FileReference sections of an Xcode project file, |
| ## putting Apple/github-local files at the front to avoid merge conflicts. |
| # |
| ## Run this in a directory with a project.pbxproj file. The sorted version |
| ## is printed on standard output. |
| # |
| |
| |
| # Files with these words in the names will be sorted into a separate section; |
| # they are only present in some repositories and so having them intermixed |
| # can lead to merge failures. |
| segregated_filenames = ["Swift", "repl", "RPC"] |
| |
| def read_pbxproj(fn) |
| beginning = Array.new # All lines before "PBXBuildFile section" |
| files = Array.new # PBXBuildFile section lines -- sort these |
| middle = Array.new # All lines between PBXBuildFile and PBXFileReference sections |
| refs = Array.new # PBXFileReference section lines -- sort these |
| ending = Array.new # All lines after PBXFileReference section |
| |
| all_lines = File.readlines fn |
| |
| state = 1 # "begin" |
| all_lines.each do |l| |
| l.chomp |
| if state == 1 && l =~ /Begin PBXBuildFile section/ |
| beginning.push(l) |
| state = 2 |
| next |
| end |
| if state == 2 && l =~ /End PBXBuildFile section/ |
| middle.push(l) |
| state = 3 |
| next |
| end |
| if state == 3 && l =~ /Begin PBXFileReference section/ |
| middle.push(l) |
| state = 4 |
| next |
| end |
| if state == 4 && l =~ /End PBXFileReference section/ |
| ending.push(l) |
| state = 5 |
| next |
| end |
| |
| if state == 1 |
| beginning.push(l) |
| elsif state == 2 |
| files.push(l) |
| elsif state == 3 |
| middle.push(l) |
| elsif state == 4 |
| refs.push(l) |
| else |
| ending.push(l) |
| end |
| end |
| |
| return beginning, files, middle, refs, ending |
| end |
| |
| xcodeproj_filename = nil |
| [ "../lldb.xcodeproj/project.pbxproj", "lldb.xcodeproj/project.pbxproj", "project.pbxproj" ].each do |ent| |
| if File.exists?(ent) |
| xcodeproj_filename = ent |
| break |
| end |
| end |
| |
| if xcodeproj_filename.nil? |
| STDERR.puts "Could not find xcode project file to sort." |
| exit(1) |
| end |
| |
| beginning, files, middle, refs, ending = read_pbxproj(xcodeproj_filename) |
| |
| |
| ### If we're given a "canonical" project.pbxproj file, get the uuid and fileref ids for |
| ### every source file in this project.pbxproj and the canonical one, and fix any of |
| ### the identifiers that don't match in the project file we're updating. |
| ### this comes up when people add the file independently on different branches and it |
| ### gets different identifiers. |
| |
| if ARGV.size() > 0 |
| canonical_pbxproj = nil |
| if ARGV.size == 2 && ARGV[0] == "--canonical" |
| canonical_pbxproj = ARGV[1] |
| elsif ARGV.size == 1 && ARGV[0] =~ /--canonical=(.+)/ |
| canonical_pbxproj = $1 |
| end |
| |
| if File.exists?(canonical_pbxproj) |
| ignore1, canon_files, ignore2, ignore3, ignore4 = read_pbxproj(canonical_pbxproj) |
| canon_files_by_filename = Hash.new { |k, v| k[v] = Array.new } |
| |
| canon_files.each do |l| |
| # 2669421A1A6DC2AC0063BE93 /* MICmdCmdTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 266941941A6DC2AC0063BE93 /* MICmdCmdTarget.cpp */; }; |
| |
| if l =~ /^\s+([A-F0-9]{24})\s+\/\*\s+(.*?)\sin.*?\*\/.*?fileRef = ([A-F0-9]{24})\s.*$/ |
| uuid = $1 |
| filename = $2 |
| fileref = $3 |
| canon_files_by_filename[filename].push({ :uuid => uuid, :fileref => fileref }) |
| end |
| end |
| |
| this_project_files = Hash.new { |k, v| k[v] = Array.new } |
| |
| files.each do |l| |
| # 2669421A1A6DC2AC0063BE93 /* MICmdCmdTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 266941941A6DC2AC0063BE93 /* MICmdCmdTarget.cpp */; }; |
| |
| if l =~ /^\s+([A-F0-9]{24})\s+\/\*\s+(.*?)\sin.*?\*\/.*?fileRef = ([A-F0-9]{24})\s.*$/ |
| uuid = $1 |
| filename = $2 |
| fileref = $3 |
| this_project_files[filename].push({ :uuid => uuid, :fileref => fileref }) |
| end |
| end |
| |
| this_project_files.keys.each do |fn| |
| next if !canon_files_by_filename.has_key?(fn) |
| next if this_project_files[fn].size() > 1 || canon_files_by_filename[fn].size() > 1 |
| this_ent = this_project_files[fn][0] |
| canon_ent = canon_files_by_filename[fn][0] |
| if this_ent[:uuid] != canon_ent[:uuid] |
| STDERR.puts "#{fn} has uuid #{this_ent[:uuid]} in this project file, #{canon_ent[:uuid]} in the canonical" |
| [ beginning, files, middle, refs, ending ].each do |arr| |
| arr.each { |l| l.gsub!(this_ent[:uuid], canon_ent[:uuid]) } |
| end |
| end |
| if this_ent[:fileref] != canon_ent[:fileref] |
| STDERR.puts "#{fn} has fileref #{this_ent[:fileref]} in this project file, #{canon_ent[:fileref]} in the canonical" |
| [ beginning, files, middle, refs, ending ].each do |arr| |
| arr.each { |l| l.gsub!(this_ent[:fileref], canon_ent[:fileref]) } |
| end |
| end |
| |
| end |
| end |
| end |
| |
| |
| |
| ######### Sort FILES by the filename, putting swift etc in front |
| |
| # key is filename |
| # value is array of text lines for that filename in the FILES text |
| # (libraries like libz.dylib seem to occur multiple times, probably |
| # once each for different targets). |
| |
| files_by_filename = Hash.new { |k, v| k[v] = Array.new } |
| |
| files.each do |l| |
| # 2669421A1A6DC2AC0063BE93 /* MICmdCmdTarget.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 266941941A6DC2AC0063BE93 /* MICmdCmdTarget.cpp */; }; |
| |
| if l =~ /^\s+([A-F0-9]{24})\s+\/\*\s+(.*?)\sin.*?\*\/.*?fileRef = ([A-F0-9]{24})\s.*$/ |
| uuid = $1 |
| filename = $2 |
| fileref = $3 |
| files_by_filename[filename].push(l) |
| end |
| |
| end |
| |
| # clear the FILES array |
| |
| files = Array.new |
| |
| # add the lines in sorted order. First swift/etc, then everything else. |
| |
| segregated_filenames.each do |keyword| |
| filenames = files_by_filename.keys |
| filenames.select {|l| l.include?(keyword) }.sort.each do |fn| |
| # re-add all the lines for the filename FN to our FILES array that we'll |
| # be outputting. |
| files_by_filename[fn].sort.each do |l| |
| files.push(l) |
| end |
| files_by_filename.delete(fn) |
| end |
| end |
| |
| # All segregated filenames have been added to the FILES output array. |
| # Now add all the other lines, sorted by filename. |
| |
| files_by_filename.keys.sort.each do |fn| |
| files_by_filename[fn].sort.each do |l| |
| files.push(l) |
| end |
| end |
| |
| ######### Sort REFS by the filename, putting swift etc in front |
| |
| refs_by_filename = Hash.new { |k, v| k[v] = Array.new } |
| refs.each do |l| |
| # 2611FF12142D83060017FEA3 /* SBValue.i */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c.preprocessed; path = SBValue.i; sourceTree = "<group>"; }; |
| |
| if l =~ /^\s+([A-F0-9]{24})\s+\/\*\s+(.*?)\s\*\/.*$/ |
| uuid = $1 |
| filename = $2 |
| refs_by_filename[filename].push(l) |
| end |
| end |
| |
| # clear the refs array |
| |
| refs = Array.new |
| |
| # add the lines in sorted order. First swift/etc, then everything else. |
| |
| |
| segregated_filenames.each do |keyword| |
| filenames = refs_by_filename.keys |
| filenames.select {|l| l.include?(keyword) }.sort.each do |fn| |
| # re-add all the lines for the filename FN to our refs array that we'll |
| # be outputting. |
| refs_by_filename[fn].sort.each do |l| |
| refs.push(l) |
| end |
| refs_by_filename.delete(fn) |
| end |
| end |
| |
| # All segregated filenames have been added to the refs output array. |
| # Now add all the other lines, sorted by filename. |
| |
| refs_by_filename.keys.sort.each do |fn| |
| refs_by_filename[fn].sort.each do |l| |
| refs.push(l) |
| end |
| end |
| |
| |
| |
| ####### output the sorted pbxproj |
| |
| File.open(xcodeproj_filename, 'w') do |outfile| |
| [ beginning, files, middle, refs, ending ].each do |arr| |
| arr.each {|l| outfile.puts l} |
| end |
| end |