From a188215d6991effb76dcdbd4f830bf66800864e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?= <70830482+FnControlOption@users.noreply.github.com> Date: Fri, 12 Nov 2021 13:50:53 -0800 Subject: [PATCH 1/2] deps: minor refactoring --- Library/Homebrew/cmd/deps.rb | 21 ++++++++++++--------- Library/Homebrew/utils/topological_hash.rb | 6 ++---- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/Library/Homebrew/cmd/deps.rb b/Library/Homebrew/cmd/deps.rb index 8a5c2997ab..986f8a713f 100644 --- a/Library/Homebrew/cmd/deps.rb +++ b/Library/Homebrew/cmd/deps.rb @@ -203,13 +203,12 @@ module Homebrew def puts_deps_tree(dependents, args:, recursive: false) dependents.each do |d| puts d.full_name - @dep_stack = [] - recursive_deps_tree(d, "", recursive, args: args) + recursive_deps_tree(d, dep_stack: [], prefix: "", recursive: recursive, args: args) puts end end - def recursive_deps_tree(f, prefix, recursive, args:) + def recursive_deps_tree(f, dep_stack:, prefix:, recursive:, args:) includes, ignores = args_includes_ignores(args) dependables = @use_runtime_dependencies ? f.runtime_dependencies : f.deps deps = reject_ignores(dependables, ignores, includes) @@ -217,7 +216,7 @@ module Homebrew dependables = reqs + deps max = dependables.length - 1 - @dep_stack.push f.name + dep_stack.push f.name dependables.each_with_index do |dep, i| next if !args.include_requirements? && dep.is_a?(Requirement) @@ -228,7 +227,7 @@ module Homebrew end display_s = "#{tree_lines} #{dep_display_name(dep, args: args)}" - is_circular = @dep_stack.include?(dep.name) + is_circular = dep_stack.include?(dep.name) display_s = "#{display_s} (CIRCULAR DEPENDENCY)" if is_circular puts "#{prefix}#{display_s}" @@ -240,11 +239,15 @@ module Homebrew "│ " end - if dep.is_a? Dependency - recursive_deps_tree(Formulary.factory(dep.name), prefix + prefix_addition, true, args: args) - end + next unless dep.is_a? Dependency + + recursive_deps_tree(Formulary.factory(dep.name), + dep_stack: dep_stack, + prefix: prefix + prefix_addition, + recursive: true, + args: args) end - @dep_stack.pop + dep_stack.pop end end diff --git a/Library/Homebrew/utils/topological_hash.rb b/Library/Homebrew/utils/topological_hash.rb index c6a88b3268..cac774c857 100644 --- a/Library/Homebrew/utils/topological_hash.rb +++ b/Library/Homebrew/utils/topological_hash.rb @@ -20,7 +20,7 @@ module Utils packages = Array(packages) packages.each do |cask_or_formula| - next accumulator if accumulator.key?(cask_or_formula) + next if accumulator.key?(cask_or_formula) if cask_or_formula.is_a?(Cask::Cask) formula_deps = cask_or_formula.depends_on @@ -39,9 +39,7 @@ module Utils .map { |c| Cask::CaskLoader.load(c, config: nil) } end - accumulator[cask_or_formula] ||= [] - accumulator[cask_or_formula] += formula_deps - accumulator[cask_or_formula] += cask_deps + accumulator[cask_or_formula] = formula_deps + cask_deps graph_package_dependencies(formula_deps, accumulator) graph_package_dependencies(cask_deps, accumulator) From 87e9a495d1a65d4e234198aec128d281404656cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?= <70830482+FnControlOption@users.noreply.github.com> Date: Fri, 12 Nov 2021 13:52:20 -0800 Subject: [PATCH 2/2] deps: add `--graph` and `--dot` switches --- Library/Homebrew/cask_dependent.rb | 2 + Library/Homebrew/cmd/deps.rb | 75 ++++++++++++++++++++++++++---- 2 files changed, 69 insertions(+), 8 deletions(-) diff --git a/Library/Homebrew/cask_dependent.rb b/Library/Homebrew/cask_dependent.rb index 8644e7776d..88c97c8df5 100644 --- a/Library/Homebrew/cask_dependent.rb +++ b/Library/Homebrew/cask_dependent.rb @@ -3,6 +3,8 @@ # An adapter for casks to provide dependency information in a formula-like interface. class CaskDependent + attr_reader :cask + def initialize(cask) @cask = cask end diff --git a/Library/Homebrew/cmd/deps.rb b/Library/Homebrew/cmd/deps.rb index 986f8a713f..e3d660addd 100644 --- a/Library/Homebrew/cmd/deps.rb +++ b/Library/Homebrew/cmd/deps.rb @@ -43,6 +43,11 @@ module Homebrew switch "--tree", description: "Show dependencies as a tree. When given multiple formula arguments, "\ "show individual trees for each formula." + switch "--graph", + description: "Show dependencies as a directed graph." + switch "--dot", + depends_on: "--graph", + description: "Show text-based graph description in DOT format." switch "--annotate", description: "Mark any build, test, optional, or recommended dependencies as "\ "such in the output." @@ -62,6 +67,7 @@ module Homebrew depends_on: "--installed", description: "Treat all named arguments as casks." + conflicts "--tree", "--graph" conflicts "--installed", "--all" conflicts "--formula", "--cask" formula_options @@ -80,12 +86,13 @@ module Homebrew @use_runtime_dependencies = installed && recursive && !args.tree? && + !args.graph? && !args.include_build? && !args.include_test? && !args.include_optional? && !args.skip_recommended? - if args.tree? + if args.tree? || args.graph? dependents = if args.named.present? sorted_dependents(args.named.to_formulae_and_casks) elsif args.installed? @@ -101,6 +108,16 @@ module Homebrew raise FormulaUnspecifiedError end + if args.graph? + dot_code = dot_code(dependents, recursive: recursive, args: args) + if args.dot? + puts dot_code + else + exec_browser "https://dreampuf.github.io/GraphvizOnline/##{ERB::Util.url_encode(dot_code)}" + end + return + end + puts_deps_tree dependents, recursive: recursive, args: args return elsif args.all? @@ -200,6 +217,46 @@ module Homebrew end end + def dot_code(dependents, recursive:, args:) + dep_graph = {} + dependents.each do |d| + graph_deps(d, dep_graph: dep_graph, recursive: recursive, args: args) + end + + dot_code = dep_graph.map do |d, deps| + deps.map do |dep| + attributes = [] + attributes << "style = dotted" if dep.build? + attributes << "arrowhead = empty" if dep.test? + if dep.optional? + attributes << "color = red" + elsif dep.recommended? + attributes << "color = green" + end + comment = " # #{dep.tags.map(&:inspect).join(", ")}" if dep.tags.any? + " \"#{d}\" -> \"#{dep}\"#{" [#{attributes.join(", ")}]" if attributes.any?}#{comment}" + end + end.flatten.join("\n") + "digraph {\n#{dot_code}\n}" + end + + def graph_deps(f, dep_graph:, recursive:, args:) + return if dep_graph.key?(f) + + dependables = dependables(f, args: args) + dep_graph[f] = dependables + return unless recursive + + dependables.each do |dep| + next unless dep.is_a? Dependency + + graph_deps(Formulary.factory(dep.name), + dep_graph: dep_graph, + recursive: true, + args: args) + end + end + def puts_deps_tree(dependents, args:, recursive: false) dependents.each do |d| puts d.full_name @@ -208,18 +265,20 @@ module Homebrew end end - def recursive_deps_tree(f, dep_stack:, prefix:, recursive:, args:) + def dependables(f, args:) includes, ignores = args_includes_ignores(args) - dependables = @use_runtime_dependencies ? f.runtime_dependencies : f.deps - deps = reject_ignores(dependables, ignores, includes) - reqs = reject_ignores(f.requirements, ignores, includes) - dependables = reqs + deps + deps = @use_runtime_dependencies ? f.runtime_dependencies : f.deps + deps = reject_ignores(deps, ignores, includes) + reqs = reject_ignores(f.requirements, ignores, includes) if args.include_requirements? + reqs ||= [] + reqs + deps + end + def recursive_deps_tree(f, dep_stack:, prefix:, recursive:, args:) + dependables = dependables(f, args: args) max = dependables.length - 1 dep_stack.push f.name dependables.each_with_index do |dep, i| - next if !args.include_requirements? && dep.is_a?(Requirement) - tree_lines = if i == max "└──" else