lib/pod/command/dependencies.rb
module Pod
class Command
class Dependencies < Command
include Command::ProjectDirectory
self.summary = "Show project's dependency graph."
self.description = <<-DESC
Shows the project's dependency graph.
DESC
def self.options
[
['--ignore-lockfile', 'Whether the lockfile should be ignored when calculating the dependency graph'],
['--repo-update', 'Fetch external podspecs and run `pod repo update` before calculating the dependency graph'],
['--graphviz', 'Outputs the dependency graph in Graphviz format to <podspec name>.gv or Podfile.gv'],
['--image', 'Outputs the dependency graph as an image to <podspec name>.png or Podfile.png'],
['--use-podfile-targets', 'Uses targets from the Podfile'],
['--ranksep', 'If you use --image command this command will be useful. The gives desired rank separation, in inches. Example --ranksep=.75, default .75'],
['--nodesep', 'It is same as [--ranksep] command. Minimum space between two adjacent nodes in the same rank, in inches.Example --nodesep=.25, default .25'],
['--filter-pattern', 'Filters out subtrees from pods with names matching the specified pattern from the --graphviz and --image output. Example --filter-pattern="Tests"'],
].concat(super)
end
def self.arguments
[
CLAide::Argument.new('PODSPEC', false)
].concat(super)
end
def initialize(argv)
@podspec_name = argv.shift_argument
@ignore_lockfile = argv.flag?('ignore-lockfile', false)
@repo_update = argv.flag?('repo-update', false)
@produce_graphviz_output = argv.flag?('graphviz', false)
@produce_image_output = argv.flag?('image', false)
@use_podfile_targets = argv.flag?('use-podfile-targets', false)
@ranksep = argv.option('ranksep', '0.75')
@nodesep = argv.option('nodesep', '0.25')
@filter_pattern = argv.option('filter-pattern', nil)
super
end
def validate!
super
if @podspec_name
require 'pathname'
path = Pathname.new(@podspec_name)
if path.file?
@podspec = Specification.from_file(path)
else
sets = Config.
instance.
sources_manager.
search(Dependency.new(@podspec_name))
spec = sets && sets.specification
@podspec = spec && spec.subspec_by_name(@podspec_name)
raise Informative, "Cannot find `#{@podspec_name}`." unless @podspec
end
end
if (@produce_image_output || @produce_graphviz_output) && Executable.which('dot').nil?
raise Informative, 'GraphViz must be installed and `dot` must be in ' \
'$PATH to produce image or graphviz output.'
end
end
def run
require 'yaml'
UI.title "Calculating dependencies" do
dependencies
end
graphviz_image_output if @produce_image_output
graphviz_dot_output if @produce_graphviz_output
yaml_output
end
def dependencies
@dependencies ||= begin
lockfile = config.lockfile unless @ignore_lockfile || @podspec
if !lockfile || @repo_update
analyzer = Installer::Analyzer.new(
sandbox,
podfile,
lockfile
)
specs = config.with_changes(skip_repo_update: !@repo_update) do
analyzer.analyze(@repo_update || @podspec).specs_by_target.values.flatten(1)
end
lockfile = Lockfile.generate(podfile, specs, {})
end
lockfile.to_hash['PODS']
end
end
def podfile
@podfile ||= begin
if podspec = @podspec
platform = podspec.available_platforms.first
platform_name = platform.name
platform_version = platform.deployment_target.to_s
source_urls = Config.instance.sources_manager.all.map(&:url).compact
Podfile.new do
install! 'cocoapods', integrate_targets: false, warn_for_multiple_pod_sources: false
source_urls.each { |u| source(u) }
platform platform_name, platform_version
pod podspec.name, podspec: podspec.defined_in_file
target 'Dependencies'
end
else
verify_podfile_exists!
config.podfile
end
end
end
def sandbox
if @podspec
require 'tmpdir'
Sandbox.new(Dir.mktmpdir)
else
config.sandbox
end
end
def graphviz_data
@graphviz ||= begin
require 'graphviz'
graph = GraphViz::new(output_file_basename, :type => :digraph, :ranksep => @ranksep, :nodesep => @nodesep)
if @use_podfile_targets
unless @podspec
podfile.target_definitions.values.each do |target|
target_node = add_node(graph, target.name.to_s)
next if target_node.nil?
if target.dependencies
target.dependencies.each do |dependency|
pod_node = add_node(graph, dependency.name.to_s)
next if pod_node.nil?
graph.add_edge(target_node, pod_node)
end
end
end
end
else
root = graph.add_node(output_file_basename)
unless @podspec
podfile_dependencies.each do |pod|
pod_node = add_node(graph, pod)
next if pod_node.nil?
graph.add_edge(root, pod_node)
end
end
end
pod_to_dependencies.each do |pod, dependencies|
pod_node = add_node(graph, pod)
next if pod_node.nil?
dependencies.each do |dependency|
dep_node = add_node(graph, dependency)
next if dep_node.nil?
graph.add_edge(pod_node, dep_node)
end
end
graph
end
end
def add_node(graph, pod)
return nil if filter? pod
graph.add_node(sanitized_pod_name(pod))
end
# Truncates the input string after a pod's name removing version requirements, etc.
def sanitized_pod_name(name)
Pod::Dependency.from_string(name).name
end
def filter?(name)
return false unless @filter_pattern
@filter_regex ||= Regexp.new(@filter_pattern)
@filter_regex.match(name) != nil
end
# Returns a Set of Strings of the names of dependencies specified in the Podfile.
def podfile_dependencies
Set.new(podfile.target_definitions.values.map { |t| t.dependencies.map { |d| d.name } }.flatten)
end
# Returns a [String: [String]] containing resolved mappings from the name of a pod to an array of the names of its dependencies.
def pod_to_dependencies
dependencies.map { |d| d.is_a?(Hash) ? d : { d => [] } }.reduce({}) { |combined, individual| combined.merge!(individual) }
end
# Basename to use for output files.
def output_file_basename
return 'Podfile' unless @podspec_name
File.basename(@podspec_name, File.extname(@podspec_name))
end
def yaml_output
UI.title 'Dependencies' do
UI.puts YAMLHelper.convert(dependencies)
end
end
def graphviz_image_output
graphviz_data.output( :png => "#{output_file_basename}.png")
end
def graphviz_dot_output
graphviz_data.output( :dot => "#{output_file_basename}.gv")
end
end
end
end