OILS / tools / deps.py View on Github | oils.pub

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