├── CONTRIBUTING.md ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Rakefile ├── cocoapods-why.gemspec ├── lib ├── cocoapods_plugin.rb ├── cocoapods_why.rb └── pod │ └── command │ └── why.rb └── spec ├── cache.yaml ├── cache_dfs.yaml ├── spec_helper.rb └── why_spec.rb /CONTRIBUTING.md: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | # Specify your gem's dependencies in cocoapods-why.gemspec 6 | gemspec 7 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | cocoapods-why (1.2) 5 | cocoapods (~> 1.0) 6 | rgl (~> 0.5) 7 | 8 | GEM 9 | remote: https://rubygems.org/ 10 | specs: 11 | CFPropertyList (3.0.5) 12 | rexml 13 | activesupport (7.0.7.2) 14 | concurrent-ruby (~> 1.0, >= 1.0.2) 15 | i18n (>= 1.6, < 2) 16 | minitest (>= 5.1) 17 | tzinfo (~> 2.0) 18 | atomos (0.1.3) 19 | claide (1.0.3) 20 | cocoapods (1.0.1) 21 | activesupport (>= 4.0.2) 22 | claide (>= 1.0.0, < 2.0) 23 | cocoapods-core (= 1.0.1) 24 | cocoapods-deintegrate (>= 1.0.0, < 2.0) 25 | cocoapods-downloader (>= 1.0.0, < 2.0) 26 | cocoapods-plugins (>= 1.0.0, < 2.0) 27 | cocoapods-search (>= 1.0.0, < 2.0) 28 | cocoapods-stats (>= 1.0.0, < 2.0) 29 | cocoapods-trunk (>= 1.0.0, < 2.0) 30 | cocoapods-try (>= 1.0.0, < 2.0) 31 | colored (~> 1.2) 32 | escape (~> 0.0.4) 33 | fourflusher (~> 0.3.0) 34 | molinillo (~> 0.4.5) 35 | nap (~> 1.0) 36 | xcodeproj (>= 1.1.0, < 2.0) 37 | cocoapods-core (1.0.1) 38 | activesupport (>= 4.0.2) 39 | fuzzy_match (~> 2.0.4) 40 | nap (~> 1.0) 41 | cocoapods-deintegrate (1.0.5) 42 | cocoapods-downloader (1.6.3) 43 | cocoapods-plugins (1.0.0) 44 | nap 45 | cocoapods-search (1.0.1) 46 | cocoapods-stats (1.1.0) 47 | cocoapods-trunk (1.6.0) 48 | nap (>= 0.8, < 2.0) 49 | netrc (~> 0.11) 50 | cocoapods-try (1.2.0) 51 | colored (1.2) 52 | colored2 (3.1.2) 53 | concurrent-ruby (1.2.2) 54 | diff-lcs (1.4.4) 55 | escape (0.0.4) 56 | fourflusher (0.3.2) 57 | fuzzy_match (2.0.4) 58 | i18n (1.14.1) 59 | concurrent-ruby (~> 1.0) 60 | lazy_priority_queue (0.1.1) 61 | minitest (5.19.0) 62 | molinillo (0.4.5) 63 | nanaimo (0.3.0) 64 | nap (1.1.0) 65 | netrc (0.11.0) 66 | rake (13.0.1) 67 | rexml (3.3.9) 68 | rgl (0.5.6) 69 | lazy_priority_queue (~> 0.1.0) 70 | stream (~> 0.5.2) 71 | rspec (3.9.0) 72 | rspec-core (~> 3.9.0) 73 | rspec-expectations (~> 3.9.0) 74 | rspec-mocks (~> 3.9.0) 75 | rspec-core (3.9.2) 76 | rspec-support (~> 3.9.3) 77 | rspec-expectations (3.9.2) 78 | diff-lcs (>= 1.2.0, < 2.0) 79 | rspec-support (~> 3.9.0) 80 | rspec-mocks (3.9.1) 81 | diff-lcs (>= 1.2.0, < 2.0) 82 | rspec-support (~> 3.9.0) 83 | rspec-support (3.9.3) 84 | stream (0.5.2) 85 | tzinfo (2.0.6) 86 | concurrent-ruby (~> 1.0) 87 | xcodeproj (1.19.0) 88 | CFPropertyList (>= 2.3.3, < 4.0) 89 | atomos (~> 0.1.3) 90 | claide (>= 1.0.2, < 2.0) 91 | colored2 (~> 3.1) 92 | nanaimo (~> 0.3.0) 93 | 94 | PLATFORMS 95 | ruby 96 | 97 | DEPENDENCIES 98 | bundler (~> 2.0) 99 | cocoapods-why! 100 | rake (~> 13.0) 101 | rspec (~> 3.9) 102 | 103 | BUNDLED WITH 104 | 2.2.31 105 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 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 | # Release Process 76 | 77 | 1. Bump version number in cocoapods_why.rb 78 | 2. Run `bundle update` to update Gemfile.lock 79 | 3. Make sure tests still pass: `rake spec` 80 | 4. (Optional) Run Rubocop on all source files 81 | 5. Build the gem: `gem build cocoapods-why.gemspec` 82 | 6. Publish the gem: `gem push cocoapods-why-1.0.gem` 83 | 84 | # Copyright 85 | 86 | Copyright 2020 Square, Inc. 87 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'bundler/gem_tasks' 4 | 5 | require 'rspec/core/rake_task' 6 | RSpec::Core::RakeTask.new(:spec) 7 | 8 | task default: :spec 9 | -------------------------------------------------------------------------------- /cocoapods-why.gemspec: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | lib = File.expand_path('lib', __dir__) 4 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 5 | require 'cocoapods_why.rb' 6 | 7 | Gem::Specification.new do |spec| 8 | spec.name = 'cocoapods-why' 9 | spec.version = CocoaPodsWhy::VERSION 10 | spec.authors = ['Trevor Harmon'] 11 | spec.email = ['trevorh@squareup.com'] 12 | spec.license = 'MIT' 13 | 14 | spec.summary = 'Shows why one CocoaPod depends on another' 15 | spec.description = 'In CocoaPods projects with a large number of dependencies, the reason why a particular pod has a transitive dependency on some other pod (possibly one you do not want) is not always clear. This plugin adds a "why" command that shows all paths between the two pods, showing exactly how the two pods are related.' 16 | spec.homepage = 'https://github.com/square/cocoapods-why' 17 | 18 | spec.files = Dir['*.md', 'lib/**/*', 'LICENSE'] 19 | spec.require_paths = ['lib'] 20 | 21 | spec.add_development_dependency 'bundler', '~> 2.0' 22 | spec.add_development_dependency 'rake', '~> 13.0' 23 | spec.add_development_dependency 'rspec', '~> 3.9' 24 | 25 | spec.add_dependency 'cocoapods', '~> 1.0' 26 | spec.add_dependency 'rgl', '~> 0.5' 27 | end 28 | -------------------------------------------------------------------------------- /lib/cocoapods_plugin.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'pod/command/why' 4 | -------------------------------------------------------------------------------- /lib/cocoapods_why.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module CocoaPodsWhy 4 | VERSION = '1.2' 5 | end 6 | -------------------------------------------------------------------------------- /lib/pod/command/why.rb: -------------------------------------------------------------------------------- 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 of the source\'s dependencies (direct and transitive) are shown.' 15 | 16 | self.arguments = [CLAide::Argument.new('source', true), CLAide::Argument.new('target', false)] 17 | 18 | def self.options 19 | [ 20 | ['--reverse', 'Shows reverse dependencies (what depends on the source instead of what the source depends on). Only valid when target is omitted.'], 21 | ['--to-yaml=FILE', 'Output the results in YAML format to the given file'], 22 | ['--to-dot=FILE', 'Output the results in DOT (GraphViz) format to the given file'], 23 | ['--cache=FILE', 'Load the dependency data from the given YAML file (created previously with the "query" command) instead of from the current CocoaPods instance'] 24 | ].concat(super) 25 | end 26 | 27 | def initialize(argv) 28 | super 29 | @source = argv.shift_argument 30 | @target = argv.shift_argument 31 | @reverse = argv.flag?('reverse') 32 | @to_yaml = argv.option('to-yaml') 33 | @to_dot = argv.option('to-dot') 34 | @cache = argv.option('cache') 35 | end 36 | 37 | def validate! 38 | super 39 | help! if @source.nil? 40 | end 41 | 42 | def run 43 | UI.puts 'Loading dependencies...' 44 | all_dependencies = all_dependencies(targets) 45 | [@source, @target].compact.each { |pod| help! "Cannot find pod named #{pod}" if all_dependencies[pod].nil? } 46 | graph = make_graph(all_dependencies) 47 | @target.nil? ? find_dependencies(@source, graph, @reverse) : find_all_dependency_paths(@source, @target, graph) 48 | end 49 | 50 | private 51 | 52 | # Returns an of array of all pods in the sandbox with their dependencies. Each element 53 | # in the array is a hash of the pod's name and its dependencies. 54 | # 55 | # If a cache is present, the array is loaded from it instead of from the current instance. 56 | # 57 | # @note For projects with a large dependency graph, this function can take a long time to 58 | # run if a cache is not given. 59 | # 60 | # @return [Array] an array of hashes containing pod names and dependencies 61 | def targets 62 | return YAML.safe_load(File.read(@cache), permitted_classes: [Symbol]) unless @cache.nil? 63 | 64 | targets = Pod::Config.instance.with_changes(silent: true) do 65 | Pod::Installer.targets_from_sandbox( 66 | Pod::Config.instance.sandbox, 67 | Pod::Config.instance.podfile, 68 | Pod::Config.instance.lockfile 69 | ).flat_map(&:pod_targets).uniq 70 | end 71 | 72 | targets.map { |target| { name: target.name, dependencies: target.root_spec.dependencies.map(&:name) } } 73 | end 74 | 75 | # Returns a hash of all dependencies found in the given target list. The keys of the hash are 76 | # pod names and their values are the direct dependencies for that pod (represented as an array 77 | # of pod names). Pods with no dependencies are mapped to an empty array. 78 | # 79 | # @param [Array] targets 80 | # An array of hashes containing pod names and dependencies 81 | # 82 | # @return [Hash>] a mapping of pod names to their direct dependencies 83 | def all_dependencies(targets) 84 | targets.to_h do |target| 85 | deps = target[:dependencies] || [] 86 | deps = deps.delete_if { |dep| dep.include? '/' } # Remove subspecs 87 | [target[:name], deps] 88 | end 89 | end 90 | 91 | # Returns a directed dependency graph of all pods in the sandbox. The vertices are pod names, and 92 | # each edge represents a direct dependency on another pod. 93 | # 94 | # @param [Hash>] all_dependencies 95 | # A hash of pod names to their direct dependencies 96 | # 97 | # @return [RGL::DirectedAdjacencyGraph] a directed graph 98 | def make_graph(all_dependencies) 99 | graph = RGL::DirectedAdjacencyGraph.new 100 | all_dependencies.each do |source, targets| 101 | if targets.empty? 102 | graph.add_vertex(source) 103 | else 104 | targets.each { |target| graph.add_edge(source, target) } 105 | end 106 | end 107 | graph 108 | end 109 | 110 | # Computes and returns all possible paths between a source vertex and a target vertex in a directed graph. 111 | # 112 | # It does this by performing a recursive walk through the graph (like DFS). After returning from a recursive 113 | # descent through all of a vertex's edges, the vertex is prepended to each returned path and in this way the 114 | # list of all paths is built up. The recursion stops when the target vertex is discovered. 115 | # 116 | # The algorithm described above is exponential in running time, so memoization is used to speed it up. 117 | # After all paths from a vertex are discovered, the results are stored in a hash. Before processing a vertex, 118 | # this hash is queried for a previously stored result, which if found is returned instead of recomputed. 119 | # 120 | # @note The input graph is assumed to be acyclic. 121 | # 122 | # @param [String] source 123 | # The vertex at which to begin the search. 124 | # @param [String] target 125 | # The vertex at which to end the search. 126 | # @param [RGL::DirectedAdjacencyGraph] graph 127 | # A directed acyclic graph. The vertices are assumed to be strings (to match the source/target types). 128 | # 129 | # @return [Array>] a list of all paths from source to target 130 | def all_paths(source, target, graph) 131 | 132 | def search(source, target, graph, all_paths) 133 | return all_paths[source] if all_paths.key?(source) 134 | return [[target]] if source == target 135 | source_paths = [] 136 | graph.each_adjacent(source) { |v| source_paths += search(v, target, graph, all_paths) } 137 | all_paths[source] = source_paths.map { |path| [source] + path } 138 | end 139 | 140 | search(source, target, graph, {}) 141 | end 142 | 143 | # Converts a list of dependency paths into a graph. The vertices in the paths are 144 | # assumed to exist in the given graph. 145 | # 146 | # @param [RGL::DirectedAdjacencyGraph] graph 147 | # A directed graph of pod dependencies 148 | # @param [Array>] all_paths 149 | # A list of paths from one dependency to another 150 | # 151 | # @return [Array>] a list of all paths from source to target 152 | def all_paths_graph(graph, all_paths) 153 | all_paths_vertices = all_paths.flatten.to_set 154 | graph.vertices_filtered_by { |v| all_paths_vertices.include? v } 155 | end 156 | 157 | # Finds and prints all paths from source to target. 158 | def find_all_dependency_paths(source, target, graph) 159 | all_paths = all_paths(source, target, graph) 160 | 161 | if all_paths.empty? 162 | UI.puts "#{source} does not depend on #{target}" 163 | return 164 | end 165 | 166 | UI.puts "Why does #{source} depend on #{target}?" 167 | 168 | all_paths.each do |path| 169 | UI.puts path.join(' ⟶ ') 170 | end 171 | 172 | File.open(@to_yaml, 'w') { |file| file.write(all_paths.to_yaml) } if @to_yaml 173 | File.open(@to_dot, 'w') { |file| file.write(all_paths_graph(graph, all_paths).to_dot_graph.to_s) } if @to_dot 174 | end 175 | 176 | # Finds and prints all pods that source depends on, or all that depend on source (directly or transitively). 177 | def find_dependencies(source, graph, reverse) 178 | if reverse 179 | UI.puts "What depends on #{source}?" 180 | graph = graph.reverse 181 | else 182 | UI.puts "What does #{source} depend on?" 183 | end 184 | 185 | tree = graph.bfs_search_tree_from(source) 186 | graph = graph.vertices_filtered_by { |v| tree.has_vertex? v } 187 | sorted_dependencies = graph.vertices.sort 188 | sorted_dependencies.delete(source) 189 | 190 | if sorted_dependencies.empty? 191 | UI.puts 'Nothing' 192 | else 193 | sorted_dependencies.each { |dependency| UI.puts dependency } 194 | end 195 | 196 | File.open(@to_yaml, 'w') { |file| file.write(sorted_dependencies.to_s) } if @to_yaml 197 | File.open(@to_dot, 'w') { |file| file.write(graph.to_dot_graph.to_s) } if @to_dot 198 | end 199 | end 200 | end 201 | end 202 | -------------------------------------------------------------------------------- /spec/cache.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - :name: A 3 | :dependencies: 4 | - B 5 | - C 6 | - :name: B 7 | :dependencies: 8 | - H 9 | - D 10 | - E 11 | - :name: C 12 | :dependencies: 13 | - F 14 | - :name: D 15 | :dependencies: 16 | - H 17 | - G 18 | - :name: E 19 | :dependencies: 20 | - H 21 | - G 22 | - :name: F 23 | - :name: G 24 | :dependencies: 25 | - H 26 | - :name: H 27 | - :name: Q 28 | -------------------------------------------------------------------------------- /spec/cache_dfs.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | - :name: A 3 | :dependencies: 4 | - B 5 | - C 6 | - :name: B 7 | :dependencies: 8 | - C 9 | - D 10 | - :name: C 11 | :dependencies: 12 | - D 13 | - :name: D 14 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative '../lib/pod/command/why' 4 | 5 | require 'yaml' 6 | 7 | RSpec.configure do |config| 8 | config.before(:each) do 9 | @tempfile = Tempfile.new('why') 10 | @args = ["--to-yaml=#{@tempfile.path}", '--silent'] 11 | end 12 | 13 | config.after(:each) do 14 | @tempfile.unlink 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /spec/why_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'spec_helper' 4 | 5 | CACHE = "#{__dir__}/cache.yaml" 6 | CACHE_DFS = "#{__dir__}/cache_dfs.yaml" 7 | 8 | describe Pod::Command::Why do 9 | it 'finds all dependency paths between two pods' do 10 | paths = run(%w[A G]) 11 | expect(paths.length).to eq 2 12 | expect(paths[0]).to eq %w[A B D G] 13 | expect(paths[1]).to eq %w[A B E G] 14 | end 15 | 16 | it 'does not use simple DFS to find dependency paths between two pods' do 17 | paths = run(%w[A D], ["--cache=#{CACHE_DFS}"]) 18 | expect(paths.length).to eq 3 19 | expect(paths[0]).to eq %w[A B C D] 20 | expect(paths[1]).to eq %w[A B D] 21 | expect(paths[2]).to eq %w[A C D] 22 | end 23 | 24 | it 'finds no dependency paths between two pods if there are none' do 25 | expect { run(%w[A Z]) }.to raise_error(CLAide::Help) 26 | end 27 | 28 | it 'finds all dependencies' do 29 | deps = run(['B']) 30 | expect(deps).to eq %w[D E G H] 31 | end 32 | 33 | it 'finds no dependencies if the pod does not exist' do 34 | expect { run(['Z']) }.to raise_error(CLAide::Help) 35 | end 36 | 37 | it 'finds all reverse dependencies' do 38 | deps = run(['G', '--reverse']) 39 | expect(deps).to eq %w[A B D E] 40 | end 41 | 42 | it 'finds no reverse dependencies if the pod does not exist' do 43 | expect { run(['Z', '--reverse']) }.to raise_error(CLAide::Help) 44 | end 45 | 46 | it 'finds no dependencies if the pod has no dependencies' do 47 | deps = run(['Q']) 48 | expect(deps).to be_empty 49 | end 50 | 51 | it 'finds no reverse dependencies if nothing depends on the pod' do 52 | deps = run(['Q', '--reverse']) 53 | expect(deps).to be_empty 54 | end 55 | 56 | private 57 | 58 | def run(args = [], cache_arg = ["--cache=#{CACHE}"]) 59 | Pod::Command::Why.new(CLAide::ARGV.new(@args + cache_arg + args)).run 60 | YAML.safe_load(File.read(@tempfile.path)) 61 | end 62 | end 63 | --------------------------------------------------------------------------------