| 1 | from __future__ import print_function
 | 
| 2 | """Deps.py."""
 | 
| 3 | 
 | 
| 4 | import sys
 | 
| 5 | 
 | 
| 6 | from _devbuild.gen.syntax_asdl import command, command_t
 | 
| 7 | from asdl import pybase
 | 
| 8 | from mycpp.mylib import log
 | 
| 9 | from frontend import consts
 | 
| 10 | from osh import word_
 | 
| 11 | 
 | 
| 12 | 
 | 
| 13 | # TODO: Move to asdl/visitor.py?
 | 
| 14 | class 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 | 
 | 
| 51 | class 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 | 
 | 
| 145 | def Deps(node):
 | 
| 146 |     # type: (command_t) -> None
 | 
| 147 |     v = DepsVisitor(sys.stdout)
 | 
| 148 |     v.Visit(node)
 | 
| 149 |     v.Done()
 |