OILS / tools / deps.py View on Github | oilshell.org

149 lines, 66 significant
1from __future__ import print_function
2"""Deps.py."""
3
4import sys
5
6from _devbuild.gen.syntax_asdl import command, command_t
7from asdl import pybase
8from mycpp.mylib import log
9from frontend import consts
10from osh import word_
11
12
13# TODO: Move to asdl/visitor.py?
14class Visitor(object):
15 # Python does introspection on method names:
16 # method = 'visit_' + node.__class__.__name__
17 # I'm using ASDL metaprogramming instead.
18
19 def Visit(self, node):
20 raise NotImplementedError()
21
22 # Like ast.NodeVisitor().generic_visit!
23 def VisitChildren(self, node):
24 """
25 Args:
26 node: an ASDL node.
27 """
28 #print 'CHILD', node.ASDL_TYPE
29
30 for name in node.__slots__:
31 child = getattr(node, name)
32 #log('Considering child %s', name)
33
34 if isinstance(child, list):
35 #log('Visiting child array %s', name)
36 for item in child:
37 # We have to check for compound objects on an INSTANCE basis, not a
38 # type basis, because sums can look like this:
39 # iterable = IterArgv | IterArray(word* words)
40 # We visit the latter but not the former.
41 if isinstance(item, pybase.CompoundObj):
42 self.Visit(item)
43 continue
44
45 if isinstance(child, pybase.CompoundObj):
46 #log('Visiting child %s', name)
47 self.Visit(child)
48 continue
49
50
51class DepsVisitor(Visitor):
52 """
53 Output:
54
55 type name resolved_name source_path line_num
56 bin cp /usr/bin/cp prog.sh 22
57 lib functions.sh /home/andy/src/functions prog.sh 22
58
59 TODO:
60 - Make this TSV2
61 - handle source and .
62 - flags like --path and --special exec
63 - need some knowledge of function scope.
64 f; f() { true; } -- f is an exeternal binary!
65 g() { f; }; f() { true; } -- f is a function!
66 """
67
68 def __init__(self, f):
69 Visitor.__init__(self)
70 self.funcs_defined = {}
71 self.progs_used = {}
72 self.f = f
73
74 def _Visit(self, node):
75 #log('VISIT %s', node.__class__.__name__)
76
77 # NOTE: The tags are not unique!!! We would need this:
78 # if isinstance(node, ast.command) and node.tag == command_e.Simple:
79 # But it's easier to check the __class__ attribute.
80
81 cls = node.__class__
82 if cls is command.Simple:
83 #log('SimpleCommand %s', node.words)
84 #log('--')
85 #node.PrettyPrint()
86
87 # Things to consider:
88 # - source and .
89 # - DONE builtins: get a list from builtin.py
90 # - DONE functions: have to enter function definitions into a dictionary
91 # - Commands that call others: sudo, su, find, xargs, etc.
92 # - builtins that call others: exec, command
93 # - except not command -v!
94
95 if not node.words:
96 return
97
98 w = node.words[0]
99 ok, argv0, _ = word_.StaticEval(w)
100 if not ok:
101 log("Couldn't statically evaluate %r", w)
102 return
103
104 if (consts.LookupSpecialBuiltin(argv0) == consts.NO_INDEX and
105 consts.LookupAssignBuiltin(argv0) == consts.NO_INDEX and
106 consts.LookupNormalBuiltin(argv0) == consts.NO_INDEX):
107 self.progs_used[argv0] = True
108
109 # NOTE: If argv1 is $0, then we do NOT print a warning!
110 if argv0 == 'sudo':
111 if len(node.words) < 2:
112 return
113 w1 = node.words[1]
114 ok, argv1, _ = word_.StaticEval(w1)
115 if not ok:
116 log("Couldn't statically evaluate %r", w)
117 return
118
119 # Should we mark them behind 'sudo'? e.g. "sudo apt install"?
120 self.progs_used[argv1] = True
121
122 elif cls is command.ShFunction:
123 self.funcs_defined[node.name] = True
124
125 def Visit(self, node):
126 self._Visit(node)
127
128 # We always need to visit children, even for SimpleCommand, etc. There
129 # could be command sub, e.g. even in redirect. echo hi > $(cat out)
130 self.VisitChildren(node)
131
132 def Emit(self, row):
133 # TSV-like format
134 self.f.write('\t'.join(row))
135 self.f.write('\n')
136
137 def Done(self):
138 """Write a report."""
139 # TODO: Use self.Emit(), make it TSV.
140 for name in self.progs_used:
141 if name not in self.funcs_defined:
142 print(name)
143
144
145def Deps(node):
146 # type: (command_t) -> None
147 v = DepsVisitor(sys.stdout)
148 v.Visit(node)
149 v.Done()