cocoapods-why 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CONTRIBUTING.md +16 -0
- data/LICENSE +13 -0
- data/README.md +77 -0
- data/lib/cocoapods_plugin.rb +3 -0
- data/lib/cocoapods_why.rb +5 -0
- data/lib/pod/command/why.rb +182 -0
- metadata +122 -0
checksums.yaml
ADDED
@@ -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
|
data/CONTRIBUTING.md
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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,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: []
|