xcode_barber 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []