Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add crystal tool dependencies #13613

Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions src/compiler/crystal/command.cr
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class Crystal::Command
expand show macro expansion for given location
format format project, directories and/or files
hierarchy show type hierarchy
dependencies show file dependency tree
implementations show implementations for given call in location
types show type of main variables
--help, -h show this help
Expand Down Expand Up @@ -181,6 +182,9 @@ class Crystal::Command
when "hierarchy".starts_with?(tool)
options.shift
hierarchy
when "dependencies".starts_with?(tool)
options.shift
dependencies
when "implementations".starts_with?(tool)
options.shift
implementations
Expand Down Expand Up @@ -350,7 +354,7 @@ class Crystal::Command

private def create_compiler(command, no_codegen = false, run = false,
hierarchy = false, cursor_command = false,
single_file = false)
single_file = false, dependencies = false)
compiler = new_compiler
compiler.progress_tracker = @progress_tracker
link_flags = [] of String
Expand Down Expand Up @@ -406,8 +410,14 @@ class Crystal::Command
end
end

opts.on("-f text|json", "--format text|json", "Output format text (default) or json") do |f|
output_format = f
if dependencies
opts.on("-f tree|flat", "--format tree|flat", "Output format tree (default) or flat") do |f|
output_format = f
end
else
opts.on("-f text|json", "--format text|json", "Output format text (default) or json") do |f|
output_format = f
end
end

opts.on("--error-trace", "Show full error trace") do
Expand Down Expand Up @@ -543,9 +553,16 @@ class Crystal::Command
end
end

output_format ||= "text"
unless output_format.in?("text", "json")
error "You have input an invalid format, only text and JSON are supported"
if dependencies
output_format ||= "tree"
unless output_format.in?("tree", "flat")
error "You have input an invalid format, only tree and flat are supported"
end
else
output_format ||= "text"
unless output_format.in?("text", "json")
error "You have input an invalid format, only text and JSON are supported"
end
end

error "maximum number of threads cannot be lower than 1" if compiler.n_threads < 1
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/crystal/compiler.cr
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ module Crystal
# Whether to link statically
property? static = false

property dependency_printer : DependencyPrinter? = nil

# Program that was created for the last compilation.
property! program : Program

Expand Down
16 changes: 16 additions & 0 deletions src/compiler/crystal/program.cr
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,22 @@ module Crystal
recorded_requires << RecordedRequire.new(filename, relative_to)
end

def run_requires(node : Require, filenames) : Nil
dependency_printer = compiler.try(&.dependency_printer)

filenames.each do |filename|
unseen_file = requires.add?(filename)

dependency_printer.try(&.enter_file(filename, unseen_file))

if unseen_file
yield filename
end

dependency_printer.try(&.leave_file)
end
end

# Finds *filename* in the configured CRYSTAL_PATH for this program,
# relative to *relative_to*.
def find_in_path(filename, relative_to = nil) : Array(String)?
Expand Down
41 changes: 23 additions & 18 deletions src/compiler/crystal/semantic/semantic_visitor.cr
Original file line number Diff line number Diff line change
Expand Up @@ -69,25 +69,11 @@ abstract class Crystal::SemanticVisitor < Crystal::Visitor

if filenames
nodes = Array(ASTNode).new(filenames.size)
filenames.each do |filename|
if @program.requires.add?(filename)
parser = @program.new_parser(File.read(filename))
parser.filename = filename
parser.wants_doc = @program.wants_doc?
begin
parsed_nodes = parser.parse
parsed_nodes = @program.normalize(parsed_nodes, inside_exp: inside_exp?)
# We must type the node immediately, in case a file requires another
# *before* one of the files in `filenames`
parsed_nodes.accept self
rescue ex : CodeError
node.raise "while requiring \"#{node.string}\"", ex
rescue ex
raise Error.new "while requiring \"#{node.string}\"", ex
end
nodes << FileNode.new(parsed_nodes, filename)
end

@program.run_requires(node, filenames) do |filename|
nodes << require_file(node, filename)
end

expanded = Expressions.from(nodes)
else
expanded = Nop.new
Expand All @@ -98,6 +84,25 @@ abstract class Crystal::SemanticVisitor < Crystal::Visitor
false
end

private def require_file(node : Require, filename : String)
parser = @program.new_parser(File.read(filename))
parser.filename = filename
parser.wants_doc = @program.wants_doc?
begin
parsed_nodes = parser.parse
parsed_nodes = @program.normalize(parsed_nodes, inside_exp: inside_exp?)
# We must type the node immediately, in case a file requires another
# *before* one of the files in `filenames`
parsed_nodes.accept self
rescue ex : CodeError
node.raise "while requiring \"#{node.string}\"", ex
rescue ex
raise Error.new "while requiring \"#{node.string}\"", ex
end

FileNode.new(parsed_nodes, filename)
end

def visit(node : ClassDef)
check_outside_exp node, "declare class"
pushing_type(node.resolved_type) do
Expand Down
44 changes: 44 additions & 0 deletions src/compiler/crystal/tools/dependencies.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
require "set"
require "colorize"
require "../syntax/ast"

class Crystal::Command
private def dependencies
config = create_compiler "tool dependencies", no_codegen: true, dependencies: true
config.compiler.no_codegen = true

config.compiler.dependency_printer = DependencyPrinter.new(STDOUT, flat: config.output_format == "flat")
config.compile
end
end

module Crystal
class DependencyPrinter
@indent = 0

def initialize(@io : IO, @flat : Bool = false)
end

def enter_file(filename : String, unseen : Bool)
if unseen
print_indent
print_file(filename)
end

@indent += 1
end

def leave_file
@indent -= 1
end

private def print_indent
return if @flat
@io.print " " * @indent if @indent > 0
end

private def print_file(filename)
@io.puts ::Path[filename].relative_to?(Dir.current) || filename
end
end
end