xcode_barber 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8a4959a5ccbe8f1f11e23568ea68f174fa1e4d2d
4
+ data.tar.gz: 8db3034becab3c844eb0479ee1bdce801c02f8aa
5
+ SHA512:
6
+ metadata.gz: b29f2c78cc076428a376aa85da3d48b5d59ef1c4f2e6616456a6063a6aa5d49cc969dc64be616c29928ca2f93e35a235c1ae42844a3b552909913e431ddb31f1
7
+ data.tar.gz: 6e54aa4f2098547fcd0ed2e85acf5b03b9c30212bee09ca69747e4053c43019eba68325026c934837bf8e3b863e529ebf7ee23915572a1c99a4b70d8a75abc9a
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in xcode_barber.gemspec
4
+ gemspec
5
+
6
+ gem "rake", "~> 12.0"
data/Gemfile.lock ADDED
@@ -0,0 +1,33 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ xcode_barber (0.1.0)
5
+ slop (~> 4.7.0)
6
+ xcodeproj (~> 1.14.0)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ CFPropertyList (3.0.2)
12
+ atomos (0.1.3)
13
+ claide (1.0.3)
14
+ colored2 (3.1.2)
15
+ nanaimo (0.2.6)
16
+ rake (12.3.3)
17
+ slop (4.7.0)
18
+ xcodeproj (1.14.0)
19
+ CFPropertyList (>= 2.3.3, < 4.0)
20
+ atomos (~> 0.1.3)
21
+ claide (>= 1.0.2, < 2.0)
22
+ colored2 (~> 3.1)
23
+ nanaimo (~> 0.2.6)
24
+
25
+ PLATFORMS
26
+ ruby
27
+
28
+ DEPENDENCIES
29
+ rake (~> 12.0)
30
+ xcode_barber!
31
+
32
+ BUNDLED WITH
33
+ 2.1.4
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2020 Michael Eisel
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # Barber
2
+ ## Fast build times for big Swift projects
3
+
4
+ ## How it works
5
+
6
+ When working on a single small part of your app, e.g. `SettingsViewController`, you don't want to be compiling the whole app. You only need to work on this screen, after all. To get a fast build time in this situation, you can swap in a small AppDelegate that merely runs this one screen, and then use Barber to strip dependencies that aren't needed by it. When you're done, just revert the project.pbxproj file changes. Note: it only trims Swift dependencies, not Objective-C ones.
7
+
8
+ ## Installation
9
+
10
+ `[sudo] gem install xcode_barber`
11
+
12
+ ## Usage
13
+
14
+ ```
15
+ # add the small AppDelegate for this one screen to your project
16
+ # build your project with the file added - it must be successfully built for this to work
17
+ barber -t MyTarget -r MyApp/AppDelegate.swift -p MyApp/MyApp.xcodeproj -d ~/.../DerivedData/MyApp-asdf/path/to/swiftdeps/files
18
+ # unnecessary files are removed! now you can do your work
19
+ # reset the .pbxproj file when you're done, e.g. `git co head -- MyApp/MyApp.xcodeproj/project.pbxproj`
20
+ ```
21
+
22
+ ## Any issues?
23
+
24
+ Computing dependencies is a tricky thing. If it didn't work for you for any reason, feel free to make a short Github issue about it, and you can expect a prompt response!
25
+
26
+ ## Author
27
+
28
+ Michael Eisel, michael.eisel@gmail.com
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "xcode_barber"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/exe/barber ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/xcode_barber.rb'
4
+
5
+ include XcodeBarber
6
+
7
+ XcodeBarber::run()
@@ -0,0 +1,3 @@
1
+ module XcodeBarber
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,202 @@
1
+ # require "xcode_barber/version"
2
+ require 'set'
3
+ require 'slop'
4
+ require 'xcodeproj'
5
+
6
+ module XcodeBarber
7
+ class Error < StandardError; end
8
+
9
+ def collect_args(testing)
10
+ 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.'
11
+ if !testing
12
+ begin
13
+ return Slop.parse do |o|
14
+ o.string '-t', '--target', 'The name of the target', required: true
15
+ o.string '-p', '--project', 'The path to the .xcodeproj', required: true
16
+ o.string '-r', '--root', 'The path to the root that the app starts from. Must be a swift UIApplicationDelegate.', required: true
17
+ o.string '-d', '--deps', deps_help, required: true
18
+ o.separator 'other options:'
19
+ # o.bool '-f', '--force', 'Force the project.pbxproj file to be changed without asking first'
20
+ o.on '--help' do
21
+ puts "Example: ./barber.rb -t MyAppDebug -p MyApp.xcodeproj -r MyApp/AppDelegate.swift -d ~/.../DerivedData/.../MyAppDebug.build/Objects-normal/x86_64/"
22
+ puts o
23
+ exit
24
+ end
25
+ end
26
+ rescue
27
+ puts "Invalid arguments, try `barber --help` for usage"
28
+ exit
29
+ end
30
+ else
31
+ 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/"}
32
+ end
33
+ end
34
+
35
+ def yesno
36
+ begin
37
+ system("stty raw -echo")
38
+ str = STDIN.getc
39
+ ensure
40
+ system("stty -raw echo")
41
+ end
42
+ return str.downcase == "y"
43
+ end
44
+
45
+ def confirm_overwrite(testing, project_path)
46
+ return if testing
47
+ puts "This script will make changes to #{project_path}! Are you sure you want to continue? [y/n]"
48
+ continue = yesno()
49
+ exit unless continue
50
+ end
51
+
52
+ =begin
53
+ def export()
54
+ # remove loops
55
+ (0...(fs.size)).each { |i| o[i][i] = 0 }
56
+
57
+ format = "S" * fs.size
58
+ out = o.map { |row| row.pack(format) }.join("")
59
+ puts fs.size
60
+ IO.write("/tmp/deptrim.bin", out)
61
+ end
62
+ =end
63
+
64
+ def summarize_bottlenecks(fs, file_to_dep, provide_to_file)
65
+ f_to_idx = fs.each_with_index.to_h
66
+
67
+ dep_hash = fs.map do |file, deps|
68
+ deps = file_to_dep[file]
69
+ files = deps.flat_map do |dep|
70
+ provide_to_file[dep]
71
+ end
72
+ [f_to_idx[file], files.map { |f| f_to_idx[f] }.uniq.sort - [f_to_idx[file]]]
73
+ end
74
+
75
+ o = Array.new(fs.size) { Array.new(fs.size) { 0 } }
76
+ dep_hash.each do |i, idxs|
77
+ idxs.each { |idx| o[i][idx] = 1 }
78
+ end
79
+
80
+ reverse = Hash.new { |h, k| h[k] = [] }
81
+
82
+ dep_hash.each do |d, ds|
83
+ ds.each do |n|
84
+ reverse[n] << d
85
+ end
86
+ end
87
+
88
+ n = 10
89
+ summary = `mktemp /tmp/summary_XXXXXX`.chomp
90
+ open(summary, "w") do |file|
91
+ file.puts "Top #{n} with highest ratio dependencies to dependents:"
92
+ top = dep_hash.map do |d, ds|
93
+ [d, ds, ds.size.to_f / reverse[d].size]
94
+ end.sort_by(&:last).reverse.take(n)
95
+ file.puts top.map(&:first).map { |i| fs[i] }
96
+ file.puts ""
97
+
98
+ file.puts "Same as above, but with heavier weighting towards those with a low total number of dependents:"
99
+ top = dep_hash.map do |d, ds|
100
+ [d, ds, ds.size.to_f / (reverse[d].size ** 2)]
101
+ end.sort_by(&:last).reverse.take(n)
102
+ file.puts top.map(&:first).map { |i| fs[i] }
103
+ file.puts ""
104
+
105
+ file.puts "Still not finding the key bottlenecks in the dependency graph? Feel free to file an issue."
106
+ end
107
+
108
+ summary
109
+ end
110
+
111
+ def trimmed_deps(project_path, swiftdeps_path, root)
112
+ # Dir.chdir(__dir__)
113
+ raise "--project must be a .xcodeproj" unless project_path.end_with?(".xcodeproj")
114
+
115
+ raise "pbxproj file not found in #{project_path}" unless File.file?("#{project_path}/project.pbxproj")
116
+
117
+ provide_section = false
118
+ depends_section = false
119
+
120
+ file_to_dep = Hash.new { |h, k| h[k] = [] }
121
+ provide_to_file = Hash.new { |h, k| h[k] = [] }
122
+
123
+ files = `ls #{swiftdeps_path}/*.swiftdeps`.split("\n")
124
+ names = files.map { |file| File.basename(file, ".swiftdeps") }
125
+
126
+ files.each do |file|
127
+ name = File.basename(file, ".swiftdeps")
128
+ File.foreach(file) do |line|
129
+ if line.start_with?("provides-top-level:")
130
+ provide_section = true
131
+ depends_section = false
132
+ next
133
+ elsif line.start_with?("depends-top-level:")
134
+ provide_section = false
135
+ depends_section = true
136
+ next
137
+ elsif line.start_with?("depends-member:")
138
+ break
139
+ elsif line.start_with?("depends-") || line.start_with?("provides-")
140
+ provide_section = false
141
+ depends_section = false
142
+ next
143
+ else
144
+ str = line.start_with?("- !pri") ? line[12..-3] : line[3..-3]
145
+ if provide_section
146
+ provide_to_file[str] << name
147
+ elsif depends_section
148
+ file_to_dep[name] << str
149
+ end
150
+ end
151
+ end
152
+ end
153
+
154
+ root_name = File.basename(root, ".swift")
155
+
156
+ fs = [root_name]
157
+ idx = 0
158
+ files_seen = fs.to_set
159
+ while idx < fs.size
160
+ name = fs[idx]
161
+ deps = file_to_dep[name]
162
+ deps.each do |dep|
163
+ files = provide_to_file[dep]
164
+ fs.concat(files.reject { |f| files_seen.include?(f) })
165
+ files_seen.merge(files)
166
+ end
167
+ idx += 1
168
+ end
169
+
170
+ fs_set = fs.to_set
171
+ summary = summarize_bottlenecks(fs, file_to_dep, provide_to_file)
172
+ puts "Trimming complete. Potential dependency bottlenecks summarized at #{summary}"
173
+ return fs_set
174
+ end
175
+
176
+ def rewrite_project(target, project_path, fs_set)
177
+ project = Xcodeproj::Project.open(project_path)
178
+ target = project.targets.detect { |t| t.name == target }
179
+ raise "Couldn't find target #{target} in #{project_path}. Is it capitalized correctly and all that?" unless target
180
+
181
+ phase = target.source_build_phase
182
+ phase.files.select do |f|
183
+ path = f.file_ref.path.to_s
184
+ next false unless path.end_with?(".swift")
185
+ next false if fs_set.include?(File.basename(path, ".swift"))
186
+ true
187
+ end.each do |f|
188
+ phase.remove_build_file(f)
189
+ end
190
+
191
+ project.save
192
+ end
193
+
194
+ def run()
195
+ testing = false
196
+ opts = collect_args(testing)
197
+ project_path = opts[:project]
198
+ confirm_overwrite(testing, project_path)
199
+ deps = trimmed_deps(project_path, opts[:deps], opts[:root])
200
+ rewrite_project(opts[:target], project_path, deps)
201
+ end
202
+ end
@@ -0,0 +1,32 @@
1
+ require_relative 'lib/xcode_barber/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "xcode_barber"
5
+ spec.version = XcodeBarber::VERSION
6
+ spec.authors = ["Michael Eisel"]
7
+ spec.email = ["michael.eisel@gmail.com"]
8
+
9
+ spec.summary = %q{Trims Xcode dependencies}
10
+ spec.description = %q{Trims Xcode dependencies}
11
+ spec.homepage = "https://github.com/michaeleisel/barber"
12
+ spec.license = "MIT"
13
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
14
+
15
+ # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
16
+
17
+ spec.metadata["homepage_uri"] = spec.homepage
18
+ spec.metadata["source_code_uri"] = "https://github.com/michaeleisel/barber"
19
+ # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
20
+
21
+ # Specify which files should be added to the gem when it is released.
22
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
23
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
24
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
25
+ end
26
+ spec.bindir = "exe"
27
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
28
+ spec.require_paths = ["lib"]
29
+
30
+ spec.add_dependency "slop", "~> 4.7.0"
31
+ spec.add_dependency "xcodeproj", "~> 1.14.0"
32
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: xcode_barber
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Michael Eisel
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2020-01-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: slop
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 4.7.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 4.7.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: xcodeproj
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 1.14.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 1.14.0
41
+ description: Trims Xcode dependencies
42
+ email:
43
+ - michael.eisel@gmail.com
44
+ executables:
45
+ - barber
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - ".gitignore"
50
+ - Gemfile
51
+ - Gemfile.lock
52
+ - LICENSE.txt
53
+ - README.md
54
+ - Rakefile
55
+ - bin/console
56
+ - bin/setup
57
+ - exe/barber
58
+ - lib/xcode_barber.rb
59
+ - lib/xcode_barber/version.rb
60
+ - xcode_barber.gemspec
61
+ homepage: https://github.com/michaeleisel/barber
62
+ licenses:
63
+ - MIT
64
+ metadata:
65
+ homepage_uri: https://github.com/michaeleisel/barber
66
+ source_code_uri: https://github.com/michaeleisel/barber
67
+ post_install_message:
68
+ rdoc_options: []
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 2.3.0
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ requirements: []
82
+ rubyforge_project:
83
+ rubygems_version: 2.5.2.3
84
+ signing_key:
85
+ specification_version: 4
86
+ summary: Trims Xcode dependencies
87
+ test_files: []