module XcodeBarber
Constants
- VERSION
Public Instance Methods
collect_args(testing)
click to toggle source
# File lib/xcode_barber.rb, line 9 def collect_args(testing) deps_help = 'The path to the directory containing the .swiftdeps files for this project. To find it, go to your build directory (usually either ~/Library/Developer/Xcode/DerivedData or ./Build) and go to the <app name>-<random letters> subdirectory. From there, run `find . -name \'*.swiftdeps\'` and find the directory containing the swiftdeps files for this target. If this is a big pain, feel free to file an issue.' if !testing begin return Slop.parse do |o| o.string '-t', '--target', 'The name of the target', required: true o.string '-p', '--project', 'The path to the .xcodeproj', required: true o.string '-r', '--root', 'The path to the root that the app starts from. Must be a swift UIApplicationDelegate.', required: true o.string '-d', '--deps', deps_help, required: true o.separator 'other options:' # o.bool '-f', '--force', 'Force the project.pbxproj file to be changed without asking first' o.on '--help' do puts "Example: barber -t MyAppDebug -p MyApp.xcodeproj -r MyApp/AppDelegate.swift -d ~/.../DerivedData/.../MyAppDebug.build/Objects-normal/x86_64/" puts o exit end end rescue puts "Invalid arguments, try `barber --help` for usage" exit end else return {:project => "Beam.xcodeproj", :target => "beam", :root => "Beam/AppDelegate.swift", :deps => "#{ENV['HOME']}/Library/Developer/Xcode/DerivedData/Beam-asdsclslxivvadcbnjflpwajrfmp/Build/Intermediates.noindex/Beam.build/Debug-iphonesimulator/beam.build/Objects-normal/x86_64/"} end end
confirm_overwrite(testing, project_path)
click to toggle source
# File lib/xcode_barber.rb, line 45 def confirm_overwrite(testing, project_path) return if testing puts "This script will make changes to #{project_path}! Are you sure you want to continue? [y/n]" continue = yesno() exit unless continue end
rewrite_project(target, project_path, fs_set)
click to toggle source
# File lib/xcode_barber.rb, line 176 def rewrite_project(target, project_path, fs_set) project = Xcodeproj::Project.open(project_path) target = project.targets.detect { |t| t.name == target } raise "Couldn't find target #{target} in #{project_path}. Is it capitalized correctly and all that?" unless target phase = target.source_build_phase phase.files.select do |f| path = f.file_ref.path.to_s next false unless path.end_with?(".swift") next false if fs_set.include?(File.basename(path, ".swift")) true end.each do |f| phase.remove_build_file(f) end project.save end
run()
click to toggle source
# File lib/xcode_barber.rb, line 194 def run() testing = false opts = collect_args(testing) project_path = opts[:project] confirm_overwrite(testing, project_path) deps = trimmed_deps(project_path, opts[:deps], opts[:root]) rewrite_project(opts[:target], project_path, deps) end
summarize_bottlenecks(fs, file_to_dep, provide_to_file)
click to toggle source
def export()
# remove loops (0...(fs.size)).each { |i| o[i][i] = 0 } format = "S" * fs.size out = o.map { |row| row.pack(format) }.join("") puts fs.size IO.write("/tmp/deptrim.bin", out)
end
# File lib/xcode_barber.rb, line 64 def summarize_bottlenecks(fs, file_to_dep, provide_to_file) f_to_idx = fs.each_with_index.to_h dep_hash = fs.map do |file, deps| deps = file_to_dep[file] files = deps.flat_map do |dep| provide_to_file[dep] end [f_to_idx[file], files.map { |f| f_to_idx[f] }.uniq.sort - [f_to_idx[file]]] end o = Array.new(fs.size) { Array.new(fs.size) { 0 } } dep_hash.each do |i, idxs| idxs.each { |idx| o[i][idx] = 1 } end reverse = Hash.new { |h, k| h[k] = [] } dep_hash.each do |d, ds| ds.each do |n| reverse[n] << d end end n = 10 summary = `mktemp /tmp/summary_XXXXXX`.chomp open(summary, "w") do |file| file.puts "Top #{n} with highest ratio dependencies to dependents:" top = dep_hash.map do |d, ds| [d, ds, ds.size.to_f / reverse[d].size] end.sort_by(&:last).reverse.take(n) file.puts top.map(&:first).map { |i| fs[i] } file.puts "" file.puts "Same as above, but with heavier weighting towards those with a low total number of dependents:" top = dep_hash.map do |d, ds| [d, ds, ds.size.to_f / (reverse[d].size ** 2)] end.sort_by(&:last).reverse.take(n) file.puts top.map(&:first).map { |i| fs[i] } file.puts "" file.puts "Still not finding the key bottlenecks in the dependency graph? Feel free to file an issue." end summary end
trimmed_deps(project_path, swiftdeps_path, root)
click to toggle source
# File lib/xcode_barber.rb, line 111 def trimmed_deps(project_path, swiftdeps_path, root) # Dir.chdir(__dir__) raise "--project must be a .xcodeproj" unless project_path.end_with?(".xcodeproj") raise "pbxproj file not found in #{project_path}" unless File.file?("#{project_path}/project.pbxproj") provide_section = false depends_section = false file_to_dep = Hash.new { |h, k| h[k] = [] } provide_to_file = Hash.new { |h, k| h[k] = [] } files = `ls #{swiftdeps_path}/*.swiftdeps`.split("\n") names = files.map { |file| File.basename(file, ".swiftdeps") } files.each do |file| name = File.basename(file, ".swiftdeps") File.foreach(file) do |line| if line.start_with?("provides-top-level:") provide_section = true depends_section = false next elsif line.start_with?("depends-top-level:") provide_section = false depends_section = true next elsif line.start_with?("depends-member:") break elsif line.start_with?("depends-") || line.start_with?("provides-") provide_section = false depends_section = false next else str = line.start_with?("- !pri") ? line[12..-3] : line[3..-3] if provide_section provide_to_file[str] << name elsif depends_section file_to_dep[name] << str end end end end root_name = File.basename(root, ".swift") fs = [root_name] idx = 0 files_seen = fs.to_set while idx < fs.size name = fs[idx] deps = file_to_dep[name] deps.each do |dep| files = provide_to_file[dep] fs.concat(files.reject { |f| files_seen.include?(f) }) files_seen.merge(files) end idx += 1 end fs_set = fs.to_set summary = summarize_bottlenecks(fs, file_to_dep, provide_to_file) puts "Trimming complete. Potential dependency bottlenecks summarized at #{summary}" return fs_set end
yesno()
click to toggle source
# File lib/xcode_barber.rb, line 35 def yesno begin system("stty raw -echo") str = STDIN.getc ensure system("stty -raw echo") end return str.downcase == "y" end