cocoapods-why 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: cf607f5706da9f9cffa9cdac50e702f3c9e2fbf305f2674121102dfda5b80a01
4
+ data.tar.gz: 07625dc6f38f2728f818bc4890d4809d19d51e9899ca9065d320c7ae0d461407
5
+ SHA512:
6
+ metadata.gz: 43a3893be1ee319fdced20be09c9ad7e751e5c0b4d2d76ce0704878c69fcb0b9be34956ac3326611ff156cf5dc9bfb36af2edfc405ab22af274b8e7e7befc7fe
7
+ data.tar.gz: 5ba60b6bc840b2c23edea06b345cab24d8c03bd9db0dc11e1344da578cedd361deb311e7b84fbb74bc782cefca4bc465739f7e50c89c482db8282ed659225145
@@ -0,0 +1,16 @@
1
+ Contributing
2
+ ============
3
+
4
+ If you would like to contribute code to cocoapods-why you can do so through GitHub by
5
+ forking the repository and sending a pull request.
6
+
7
+ When submitting code, please make every effort to follow existing conventions
8
+ and style in order to keep the code as readable as possible. Please also make
9
+ sure your code has tests.
10
+
11
+ Before your code can be accepted into the project you must also sign the
12
+ [Individual Contributor License Agreement (CLA)][1].
13
+
14
+
15
+ [1]: https://spreadsheets.google.com/spreadsheet/viewform?formkey=dDViT2xzUHAwRkI3X3k5Z0lQM091OGc6MQ&ndplr=1
16
+
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2020 Square, Inc.
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
@@ -0,0 +1,77 @@
1
+ # Introduction
2
+
3
+ This plugin for CocoaPods helps understand the dependencies between two pods. It is intended for projects with a large number of dependencies.
4
+
5
+ The plugin's output can be saved to YAML format for easy parsing by other tools (e.g. a CocoaPods GUI) or to GraphViz format for visualization.
6
+
7
+ # Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'cocoapods-why'
12
+
13
+ And then run:
14
+
15
+ $ bundle
16
+
17
+ Or, install it system-wide with:
18
+
19
+ $ gem build cocoapods-why.gemspec
20
+ $ gem install cocoapods-why-1.0.0.gem
21
+
22
+ Or, in a single command:
23
+
24
+ $ bundle exec rake install
25
+
26
+ # Usage
27
+
28
+ The plugin adds a `why` command to CocoaPods. You can get help on its parameters with:
29
+
30
+ $ pod why --help
31
+
32
+ ## All Paths Between Pods
33
+
34
+ The most common usage of the `why` command is to show all paths between two pods Foo and Bar:
35
+
36
+ $ pod why Foo Bar
37
+
38
+ This is helpful for understanding why a particular pod has a transitive dependency on some other pod (possibly one you do not want). By default, it simply lists the paths, but it can also produce a graph of them.
39
+
40
+ ## All Paths To A Pod
41
+
42
+ The `why` command can also show all pods that depend on some other pod, either directly or transitively.
43
+
44
+ $ pod why Foo
45
+
46
+ This is helpful for finding the set of pods that consume a particular pod and will have to be rebuilt (or could break) if it changes. By default, the command lists all of the pods, but it can also produce a graph of them.
47
+
48
+ # Graphing
49
+
50
+ The `why` command can produce a graph of its output with the `--to-dot` argument, which takes a file name as a parameter. The output file will be in [DOT format](https://en.wikipedia.org/wiki/DOT_\(graph_description_language\)), which can be visualized with a DOT processor. For example, you can generate a PDF from a DOT file with this GraphViz command:
51
+
52
+ $ dot -Tpdf dependencies.dot > dependencies.pdf
53
+
54
+ # Caching
55
+
56
+ Finding pods in the CocoaPods project can take a long time when there are many dependencies. To speed things up, the `why` command accepts a `--cache` parameter, which is used to specify a YAML file containing previous output from the [`query --to-yaml`](https://github.com/square/cocoapods-query) command (from the [query plugin](https://github.com/square/cocoapods-query)). When the plugin sees the `--cache` parameter, it will use the data in this file instead of rebuiding the data from the current CocoaPods instance.
57
+
58
+ # Related Work
59
+
60
+ This plugin was inspired by:
61
+
62
+ * [yarn why](https://classic.yarnpkg.com/en/docs/cli/why/): It is similar to `pod why` but additionally provides information on the file sizes of the dependencies.
63
+ * [bazel query](https://docs.bazel.build/versions/master/query-how-to.html): Bazel offers a query language that can find the paths between two dependencies with `bazel query "allpaths(...)" --graph`.
64
+ * [dependencies](https://github.com/segiddins/cocoapods-dependencies): This CocoaPods plugin produces a graph of a single pod's dependencies.
65
+ * [graph](https://github.com/erickjung/cocoapods-graph): This CocoaPods plugin produces a wheel graph of all dependencies in a project.
66
+
67
+ # Development
68
+
69
+ For local development of this plugin, the simplest approach is to install it into an existing app via absolute path. For example, if the code is in a directory called `projects/cocoapods-why` off the home directory, add the following line to the app's Gemfile:
70
+
71
+ gem 'cocoapods-why', path: "#{ENV['HOME']}/projects/cocoapods-why"
72
+
73
+ You can then make changes to the code and they will be executed when using the `why` command from the app's directory.
74
+
75
+ # Copyright
76
+
77
+ Copyright 2020 Square, Inc.
@@ -0,0 +1,3 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pod/command/why'
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CocoaPodsWhy
4
+ VERSION = '1.0.0'
5
+ end
@@ -0,0 +1,182 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'cocoapods'
4
+ require 'rgl/adjacency'
5
+ require 'rgl/dot'
6
+ require 'rgl/implicit'
7
+ require 'rgl/traversal'
8
+ require 'yaml'
9
+
10
+ module Pod
11
+ class Command
12
+ class Why < Command
13
+ self.summary = 'Shows why one pod depends on another'
14
+ self.description = 'If both source and target are given, all paths between them are shown. If target is omitted, all pods that depend on the source (directly or transitively) are shown.'
15
+
16
+ self.arguments = [CLAide::Argument.new('source', true), CLAide::Argument.new('target', false)]
17
+
18
+ def self.options
19
+ [
20
+ ['--to-yaml=FILE', 'Output the results in YAML format to the given file'],
21
+ ['--to-dot=FILE', 'Output the results in DOT (GraphViz) format to the given file'],
22
+ ['--cache=FILE', 'Load the dependency data from the given YAML file (created previously with the "query" command) instead of from the current CocoaPods instance']
23
+ ].concat(super)
24
+ end
25
+
26
+ def initialize(argv)
27
+ super
28
+ @source = argv.shift_argument
29
+ @target = argv.shift_argument
30
+ @to_yaml = argv.option('to-yaml')
31
+ @to_dot = argv.option('to-dot')
32
+ @cache = argv.option('cache')
33
+ end
34
+
35
+ def validate!
36
+ super
37
+ help! if @source.nil?
38
+ end
39
+
40
+ def run
41
+ UI.puts 'Loading dependencies...'
42
+ all_dependencies = all_dependencies(targets)
43
+ [@source, @target].compact.each { |pod| help! "Cannot find pod named #{pod}" if all_dependencies[pod].nil? }
44
+ graph = make_graph(all_dependencies)
45
+ @target.nil? ? find_reverse_dependencies(@source, graph) : find_all_dependency_paths(@source, @target, graph)
46
+ end
47
+
48
+ private
49
+
50
+ # Returns an of array of all pods in the sandbox with their dependencies. Each element
51
+ # in the array is a hash of the pod's name and its dependencies.
52
+ #
53
+ # If a cache is present, the array is loaded from it instead of from the current instance.
54
+ #
55
+ # @note For projects with a large dependency graph, this function can take a long time to
56
+ # run if a cache is not given.
57
+ #
58
+ # @return [Array<Hash>] an array of hashes containing pod names and dependencies
59
+ def targets
60
+ return YAML.safe_load(File.read(@cache), permitted_classes: [Symbol]) unless @cache.nil?
61
+
62
+ targets = Pod::Config.instance.with_changes(silent: true) do
63
+ Pod::Installer.targets_from_sandbox(
64
+ Pod::Config.instance.sandbox,
65
+ Pod::Config.instance.podfile,
66
+ Pod::Config.instance.lockfile
67
+ ).flat_map(&:pod_targets).uniq
68
+ end
69
+
70
+ targets.map { |target| { name: target.name, dependencies: target.root_spec.dependencies.map(&:name) } }
71
+ end
72
+
73
+ # Returns a hash of all dependencies found in the given target list. The keys of the hash are
74
+ # pod names and their values are the direct dependencies for that pod (represented as an array
75
+ # of pod names). Pods with no dependencies are mapped to an empty array.
76
+ #
77
+ # @param [Array<Hash>] targets
78
+ # An array of hashes containing pod names and dependencies
79
+ #
80
+ # @return [Hash<String,Array<String>>] a mapping of pod names to their direct dependencies
81
+ def all_dependencies(targets)
82
+ targets.to_h do |target|
83
+ target_dependencies = target[:dependencies].delete_if { |dep| dep.include? '/' } # Remove subspecs
84
+ [target[:name], target_dependencies]
85
+ end
86
+ end
87
+
88
+ # Returns a directed dependency graph of all pods in the sandbox. The vertices are pod names, and
89
+ # each edge represents a direct dependency on another pod.
90
+ #
91
+ # @param [Hash<String,Array<String>>] all_dependencies
92
+ # A hash of pod names to their direct dependencies
93
+ #
94
+ # @return [RGL::DirectedAdjacencyGraph] a directed graph
95
+ def make_graph(all_dependencies)
96
+ graph = RGL::DirectedAdjacencyGraph.new
97
+ all_dependencies.each do |source, targets|
98
+ targets.each { |target| graph.add_edge(source, target) }
99
+ end
100
+ graph
101
+ end
102
+
103
+ # Computes and returns all possible paths between a source vertex and a target vertex in a directed graph.
104
+ # It does this by performing a DFS and, whenever the target is discovered (or re-discovered), the current
105
+ # DFS stack is captured as one of the possible paths.
106
+ #
107
+ # @note Back edges are ignored because the input graph is assumed to be acyclic.
108
+ #
109
+ # @param [String] source
110
+ # The vertex at which to begin the search.
111
+ # @param [String] target
112
+ # The vertex at which to end the search.
113
+ # @param [RGL::DirectedAdjacencyGraph] graph
114
+ # A directed acyclic graph. The vertices are assumed to be strings (to match the source/target types).
115
+ #
116
+ # @return [Array<Array<String>>] a list of all paths from source to target
117
+ def all_paths(source, target, graph)
118
+ dfs_stack = [source] # RGL uses recursion for DFS and does not expose a stack, so we build one as we go.
119
+ all_paths = []
120
+ visitor = RGL::DFSVisitor.new(graph)
121
+ visitor.set_tree_edge_event_handler do |_, v|
122
+ dfs_stack << v
123
+ all_paths << dfs_stack.dup if v == target
124
+ end
125
+ visitor.set_forward_edge_event_handler do |_, v|
126
+ dfs_stack << v
127
+ all_paths << dfs_stack.dup if v == target
128
+ dfs_stack.pop
129
+ end
130
+ graph.depth_first_visit(source, visitor) { dfs_stack.pop }
131
+ all_paths
132
+ end
133
+
134
+ # Converts a list of dependency paths into a graph. The vertices in the paths are
135
+ # assumed to exist in the given graph.
136
+ #
137
+ # @param [RGL::DirectedAdjacencyGraph] graph
138
+ # A directed graph of pod dependencies
139
+ # @param [Array<Array<String>>] all_paths
140
+ # A list of paths from one dependency to another
141
+ #
142
+ # @return [Array<Array<String>>] a list of all paths from source to target
143
+ def all_paths_graph(graph, all_paths)
144
+ all_paths_vertices = all_paths.flatten.to_set
145
+ graph.vertices_filtered_by { |v| all_paths_vertices.include? v }
146
+ end
147
+
148
+ # Finds and prints all paths from source to target.
149
+ def find_all_dependency_paths(source, target, graph)
150
+ all_paths = all_paths(source, target, graph)
151
+
152
+ if all_paths.empty?
153
+ UI.puts "#{source} does not depend on #{target}"
154
+ return
155
+ end
156
+
157
+ UI.puts "Why does #{source} depend on #{target}?"
158
+
159
+ all_paths.each do |path|
160
+ UI.puts path.join(' ⟶ ')
161
+ end
162
+
163
+ File.open(@to_yaml, 'w') { |file| file.write(all_paths.to_yaml) } if @to_yaml
164
+ File.open(@to_dot, 'w') { |file| file.write(all_paths_graph(graph, all_paths).to_dot_graph.to_s) } if @to_dot
165
+ end
166
+
167
+ # Finds and prints all pods that depend on source (directly or transitively).
168
+ def find_reverse_dependencies(source, graph)
169
+ UI.puts "What depends on #{source}?"
170
+
171
+ tree = graph.reverse.bfs_search_tree_from(source)
172
+ graph = graph.vertices_filtered_by { |v| tree.has_vertex? v }
173
+ sorted_dependencies = graph.vertices.sort
174
+ sorted_dependencies.delete(source)
175
+ sorted_dependencies.each { |dependency| UI.puts dependency }
176
+
177
+ File.open(@to_yaml, 'w') { |file| file.write(sorted_dependencies.to_s) } if @to_yaml
178
+ File.open(@to_dot, 'w') { |file| file.write(graph.to_dot_graph.to_s) } if @to_dot
179
+ end
180
+ end
181
+ end
182
+ end
metadata ADDED
@@ -0,0 +1,122 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: cocoapods-why
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Trevor Harmon
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-06-24 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '13.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '13.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.9'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.9'
55
+ - !ruby/object:Gem::Dependency
56
+ name: cocoapods
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rgl
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '0.5'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '0.5'
83
+ description: In CocoaPods projects with a large number of dependencies, the reason
84
+ why a particular pod has a transitive dependency on some other pod (possibly one
85
+ you do not want) is not always clear. This plugin adds a "why" command that shows
86
+ all paths between the two pods, showing exactly how the two pods are related.
87
+ email:
88
+ - trevorh@squareup.com
89
+ executables: []
90
+ extensions: []
91
+ extra_rdoc_files: []
92
+ files:
93
+ - CONTRIBUTING.md
94
+ - LICENSE
95
+ - README.md
96
+ - lib/cocoapods_plugin.rb
97
+ - lib/cocoapods_why.rb
98
+ - lib/pod/command/why.rb
99
+ homepage: https://github.com/square/cocoapods-why
100
+ licenses:
101
+ - MIT
102
+ metadata: {}
103
+ post_install_message:
104
+ rdoc_options: []
105
+ require_paths:
106
+ - lib
107
+ required_ruby_version: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ required_rubygems_version: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: '0'
117
+ requirements: []
118
+ rubygems_version: 3.0.4
119
+ signing_key:
120
+ specification_version: 4
121
+ summary: Shows why one CocoaPod depends on another
122
+ test_files: []