From 8f93511ce9378fad2caca2940b11ca98db72dab5 Mon Sep 17 00:00:00 2001 From: Zev Blut Date: Thu, 1 Oct 2026 17:50:39 -0500 Subject: [PATCH 1/3] Add ant_graph_task to draw Ant target dependencies JRuby's rakelib/graph.rake (graph:viz, added in 2011 as an "ant target visualizer") drew the targets of JRuby's Ant build.xml with Graphviz. JRuby no longer has an Ant build and removes it in jruby/jruby#9761, so move the idea here as a reusable feature: - Rake::Ant.dot(project) returns Graphviz DOT text with an edge from each dependency to the target that depends on it, sanitizing - and . in target names as graph.rake did - ant_graph_task(name, buildfile, output) defines a task that ant_imports the build file and pipes the DOT text to `dot -Tpng -x` - CI installs Graphviz so the PNG test runs; it skips without `dot` Co-Authored-By: Claude Opus 5.5 --- .github/workflows/test.yml | 2 ++ lib/rake/ant.rb | 1 + lib/rake/ant/graph.rb | 17 ++++++++++++++ lib/rake/ant/rake.rb | 12 ++++++++++ test/graph_example.xml | 7 ++++++ test/test_graph.rb | 45 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 84 insertions(+) create mode 100644 lib/rake/ant/graph.rb create mode 100644 test/graph_example.xml create mode 100644 test/test_graph.rb diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 477054f..044b477 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,6 +28,8 @@ jobs: java-version: 21 - name: set up ant uses: cedx/SetupAnt@v7 + - name: set up graphviz + run: sudo apt-get update && sudo apt-get install -y graphviz - name: set up ruby uses: ruby/setup-ruby@v1 with: diff --git a/lib/rake/ant.rb b/lib/rake/ant.rb index 284dcb6..59bef14 100644 --- a/lib/rake/ant.rb +++ b/lib/rake/ant.rb @@ -68,4 +68,5 @@ def self.ant_script Ant = Rake::Ant require 'rake/ant/ant' +require 'rake/ant/graph' require 'rake/ant/rake' if defined?(::Rake) diff --git a/lib/rake/ant/graph.rb b/lib/rake/ant/graph.rb new file mode 100644 index 0000000..db22d74 --- /dev/null +++ b/lib/rake/ant/graph.rb @@ -0,0 +1,17 @@ +class Rake::Ant + # Graphviz DOT text for the target dependencies of an Ant project, with an + # edge from each dependency to the target that depends on it. + def self.dot(project) + edges = [] + project.targets.each do |name, target| + target.dependencies.to_a.each do |dep| + edges << "#{dot_id(dep)} -> #{dot_id(name)}" + end + end + "digraph ant {\n#{edges.map { |edge| "#{edge}\n" }.join}}\n" + end + + def self.dot_id(name) + name.gsub(/[-.]/, '_') + end +end diff --git a/lib/rake/ant/rake.rb b/lib/rake/ant/rake.rb index c21d478..4848d67 100644 --- a/lib/rake/ant/rake.rb +++ b/lib/rake/ant/rake.rb @@ -4,6 +4,18 @@ def ant_task(*args, &block) end end +# Defines a task that draws the target dependencies of an Ant build file as +# a PNG. Requires Graphviz's `dot` on the PATH. +def ant_graph_task(name = :'ant:graph', buildfile = 'build.xml', output = 'build_graph.png') + task(name) do + ant_import buildfile + IO.popen(['dot', '-Tpng', '-x', '-o', output], 'w') do |dot| + dot.write Rake::Ant.dot(ant.project) + end + fail "dot exited with #{$?.exitstatus}" unless $?.success? + end +end + class FileList def to_str join(',') diff --git a/test/graph_example.xml b/test/graph_example.xml new file mode 100644 index 0000000..dfb9d78 --- /dev/null +++ b/test/graph_example.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/test/test_graph.rb b/test/test_graph.rb new file mode 100644 index 0000000..2c14636 --- /dev/null +++ b/test/test_graph.rb @@ -0,0 +1,45 @@ +require 'ant_test_helper' +require 'tmpdir' + +class TestGraph < Minitest::Test + include Ant::TestHelper + include Rake::DSL + + BUILDFILE = File.expand_path('graph_example.xml', __dir__) + + def setup + @app = Rake.application + Rake.application = Rake::Application.new + end + + def teardown + Rake.application = @app + end + + def test_dot_has_an_edge_from_each_dependency_to_its_target + ant_import BUILDFILE + dot = Rake::Ant.dot(ant.project) + + assert dot.start_with?("digraph ant {\n") + assert dot.end_with?("}\n") + edges = dot.lines[1..-2].map(&:chomp) + assert_equal ["compile -> dist_jar", "compile -> test_unit", "init -> compile", "init -> dist_jar"], edges.sort + end + + def test_ant_graph_task_defines_a_task + ant_graph_task :graph, BUILDFILE, 'unused.png' + refute_nil Rake::Task[:graph] + end + + def test_ant_graph_task_writes_a_png + skip 'Graphviz dot is not installed' unless system('dot', '-V', [:out, :err] => File::NULL) + + Dir.mktmpdir do |dir| + output = File.join(dir, 'build_graph.png') + ant_graph_task :graph, BUILDFILE, output + Rake::Task[:graph].invoke + + assert_equal "\x89PNG".b, File.binread(output, 4) + end + end +end From 91388feb3a496669526e7c7e619adcc7c7df223a Mon Sep 17 00:00:00 2001 From: Zev Blut Date: Fri, 2 Oct 2026 11:15:50 -0500 Subject: [PATCH 2/3] Stop mixing Rake::DSL into Object; use Rake::Task.define_task Rake no longer exposes its DSL at the top level outside a Rakefile, so the global include was both unnecessary and the thing modern Rake removed on purpose. ant_import and ant_task now define tasks via Rake::Task.define_task. Fixes #1. Co-Authored-By: Claude Sonnet 5.5 --- lib/rake/ant/ant.rb | 6 +----- lib/rake/ant/rake.rb | 2 +- test/test_rake.rb | 3 +-- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/lib/rake/ant/ant.rb b/lib/rake/ant/ant.rb index ac8da4a..2c55124 100644 --- a/lib/rake/ant/ant.rb +++ b/lib/rake/ant/ant.rb @@ -207,10 +207,6 @@ def ant(*args, &block) Rake::Ant.ant(*args, &block) end -# Need Rake DSL at top-level for "task" and other methods. -require 'rake/dsl_definition' -include Rake::DSL - def ant_import(filename = 'build.xml') ant = Rake::Ant.ant @@ -220,6 +216,6 @@ def ant_import(filename = 'build.xml') ant.project.targets.each do |target_name, target| name = Rake.application.lookup(target_name) ? "ant_" + target_name : target_name - task(name) { target.project.execute_target(target_name) } + Rake::Task.define_task(name) { target.project.execute_target(target_name) } end end diff --git a/lib/rake/ant/rake.rb b/lib/rake/ant/rake.rb index 4848d67..569b3bc 100644 --- a/lib/rake/ant/rake.rb +++ b/lib/rake/ant/rake.rb @@ -1,5 +1,5 @@ def ant_task(*args, &block) - task(*args) do |t| + Rake::Task.define_task(*args) do |t| ant.define_tasks(&block) end end diff --git a/test/test_rake.rb b/test/test_rake.rb index f826645..f01e7ac 100644 --- a/test/test_rake.rb +++ b/test/test_rake.rb @@ -12,7 +12,6 @@ def test_sets_file_lists_as_task_attributes_by_joining_them_with_commas class TestRakeAntTask < Minitest::Test include Ant::TestHelper - include Rake::DSL def setup @app = Rake.application @@ -26,7 +25,7 @@ def teardown def test_creates_a_rake_task_whose_body_defines_ant_tasks refute_includes ant.properties, "foo" - task :initial + Rake::Task.define_task :initial ant_task :ant => :initial do property :name => "foo", :value => "bar" end From 51727e6ec55988ed367f8c8d6f9a875078efb9f2 Mon Sep 17 00:00:00 2001 From: Zev Blut Date: Fri, 2 Oct 2026 11:23:06 -0500 Subject: [PATCH 3/3] Use Rake::Task.define_task in ant_graph_task and drop DSL include from its test Co-Authored-By: Claude Sonnet 5.5 --- lib/rake/ant/rake.rb | 2 +- test/test_graph.rb | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/rake/ant/rake.rb b/lib/rake/ant/rake.rb index 569b3bc..d2895ae 100644 --- a/lib/rake/ant/rake.rb +++ b/lib/rake/ant/rake.rb @@ -7,7 +7,7 @@ def ant_task(*args, &block) # Defines a task that draws the target dependencies of an Ant build file as # a PNG. Requires Graphviz's `dot` on the PATH. def ant_graph_task(name = :'ant:graph', buildfile = 'build.xml', output = 'build_graph.png') - task(name) do + Rake::Task.define_task(name) do ant_import buildfile IO.popen(['dot', '-Tpng', '-x', '-o', output], 'w') do |dot| dot.write Rake::Ant.dot(ant.project) diff --git a/test/test_graph.rb b/test/test_graph.rb index 2c14636..0e0209b 100644 --- a/test/test_graph.rb +++ b/test/test_graph.rb @@ -3,7 +3,6 @@ class TestGraph < Minitest::Test include Ant::TestHelper - include Rake::DSL BUILDFILE = File.expand_path('graph_example.xml', __dir__)