meta2deps.py revision 281812
1246149Ssjg#!/usr/bin/env python
2246149Ssjg
3261212Ssjgfrom __future__ import print_function
4261212Ssjg
5246149Ssjg"""
6246149SsjgThis script parses each "meta" file and extracts the
7246149Ssjginformation needed to deduce build and src dependencies.
8246149Ssjg
9246149SsjgIt works much the same as the original shell script, but is
10246149Ssjg*much* more efficient.
11246149Ssjg
12246149SsjgThe parsing work is handled by the class MetaFile.
13246149SsjgWe only pay attention to a subset of the information in the
14246149Ssjg"meta" files.  Specifically:
15246149Ssjg
16246149Ssjg'CWD'	to initialize our notion.
17246149Ssjg
18246149Ssjg'C'	to track chdir(2) on a per process basis
19246149Ssjg
20246149Ssjg'R'	files read are what we really care about.
21246149Ssjg	directories read, provide a clue to resolving
22246149Ssjg	subsequent relative paths.  That is if we cannot find
23246149Ssjg	them relative to 'cwd', we check relative to the last
24246149Ssjg	dir read.
25246149Ssjg
26246149Ssjg'W'	files opened for write or read-write,
27246149Ssjg	for filemon V3 and earlier.
28246149Ssjg
29246149Ssjg'E'	files executed.
30246149Ssjg
31246149Ssjg'L'	files linked
32246149Ssjg
33246149Ssjg'V'	the filemon version, this record is used as a clue
34246149Ssjg	that we have reached the interesting bit.
35246149Ssjg
36246149Ssjg"""
37246149Ssjg
38246149Ssjg"""
39246149SsjgRCSid:
40281812Ssjg	$Id: meta2deps.py,v 1.18 2015/04/03 18:23:25 sjg Exp $
41246149Ssjg
42249033Ssjg	Copyright (c) 2011-2013, Juniper Networks, Inc.
43249033Ssjg	All rights reserved.
44246149Ssjg
45246149Ssjg	Redistribution and use in source and binary forms, with or without
46246149Ssjg	modification, are permitted provided that the following conditions
47246149Ssjg	are met:
48246149Ssjg	1. Redistributions of source code must retain the above copyright
49246149Ssjg	   notice, this list of conditions and the following disclaimer.
50246149Ssjg	2. Redistributions in binary form must reproduce the above copyright
51246149Ssjg	   notice, this list of conditions and the following disclaimer in the
52246149Ssjg	   documentation and/or other materials provided with the distribution.
53246149Ssjg
54246149Ssjg	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
55246149Ssjg	"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
56246149Ssjg	LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
57246149Ssjg	A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
58246149Ssjg	OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
59246149Ssjg	SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
60246149Ssjg	LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
61246149Ssjg	DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
62246149Ssjg	THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
63246149Ssjg	(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
64246149Ssjg	OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
65246149Ssjg
66246149Ssjg"""
67246149Ssjg
68246149Ssjgimport os, re, sys
69246149Ssjg
70246149Ssjgdef getv(dict, key, d=None):
71246149Ssjg    """Lookup key in dict and return value or the supplied default."""
72246149Ssjg    if key in dict:
73246149Ssjg        return dict[key]
74246149Ssjg    return d
75246149Ssjg
76246149Ssjgdef resolve(path, cwd, last_dir=None, debug=0, debug_out=sys.stderr):
77246149Ssjg    """
78246149Ssjg    Return an absolute path, resolving via cwd or last_dir if needed.
79246149Ssjg    """
80246149Ssjg    if path.endswith('/.'):
81246149Ssjg        path = path[0:-2]
82253883Ssjg    if len(path) > 0 and path[0] == '/':
83246149Ssjg        return path
84246149Ssjg    if path == '.':
85246149Ssjg        return cwd
86246149Ssjg    if path.startswith('./'):
87246149Ssjg        return cwd + path[1:]
88246149Ssjg    if last_dir == cwd:
89246149Ssjg        last_dir = None
90246149Ssjg    for d in [last_dir, cwd]:
91246149Ssjg        if not d:
92246149Ssjg            continue
93246149Ssjg        p = '/'.join([d,path])
94246149Ssjg        if debug > 2:
95261212Ssjg            print("looking for:", p, end=' ', file=debug_out)
96246149Ssjg        if not os.path.exists(p):
97246149Ssjg            if debug > 2:
98261212Ssjg                print("nope", file=debug_out)
99246149Ssjg            p = None
100246149Ssjg            continue
101246149Ssjg        if debug > 2:
102261212Ssjg            print("found:", p, file=debug_out)
103246149Ssjg        return p
104246149Ssjg    return None
105246149Ssjg
106246149Ssjgdef abspath(path, cwd, last_dir=None, debug=0, debug_out=sys.stderr):
107246149Ssjg    """
108246149Ssjg    Return an absolute path, resolving via cwd or last_dir if needed.
109246149Ssjg    this gets called a lot, so we try to avoid calling realpath
110246149Ssjg    until we know we have something.
111246149Ssjg    """
112253883Ssjg    rpath = resolve(path, cwd, last_dir, debug, debug_out)
113253883Ssjg    if rpath:
114253883Ssjg        path = rpath
115281812Ssjg    if (path.find('/') < 0 or
116281812Ssjg	path.find('./') > 0 or
117253883Ssjg        path.endswith('/..') or
118253883Ssjg        os.path.islink(path)):
119246149Ssjg        return os.path.realpath(path)
120246149Ssjg    return path
121246149Ssjg
122246149Ssjgdef sort_unique(list, cmp=None, key=None, reverse=False):
123246149Ssjg    list.sort(cmp, key, reverse)
124246149Ssjg    nl = []
125246149Ssjg    le = None
126246149Ssjg    for e in list:
127246149Ssjg        if e == le:
128246149Ssjg            continue
129246149Ssjg        nl.append(e)
130246149Ssjg    return nl
131246149Ssjg
132250837Ssjgdef add_trims(x):
133250837Ssjg    return ['/' + x + '/',
134250837Ssjg            '/' + x,
135250837Ssjg            x + '/',
136250837Ssjg            x]
137250837Ssjg
138246149Ssjgclass MetaFile:
139246149Ssjg    """class to parse meta files generated by bmake."""
140246149Ssjg
141246149Ssjg    conf = None
142246149Ssjg    dirdep_re = None
143246149Ssjg    host_target = None
144246149Ssjg    srctops = []
145246149Ssjg    objroots = []
146281812Ssjg    excludes = []
147246149Ssjg    seen = {}
148246149Ssjg    obj_deps = []
149246149Ssjg    src_deps = []
150246149Ssjg    file_deps = []
151246149Ssjg
152246149Ssjg    def __init__(self, name, conf={}):
153246149Ssjg        """if name is set we will parse it now.
154246149Ssjg        conf can have the follwing keys:
155246149Ssjg
156246149Ssjg        SRCTOPS	list of tops of the src tree(s).
157246149Ssjg
158246149Ssjg        CURDIR	the src directory 'bmake' was run from.
159246149Ssjg
160246149Ssjg        RELDIR	the relative path from SRCTOP to CURDIR
161246149Ssjg
162246149Ssjg        MACHINE	the machine we built for.
163246149Ssjg        	set to 'none' if we are not cross-building.
164249033Ssjg		More specifically if machine cannot be deduced from objdirs.
165246149Ssjg
166250837Ssjg        TARGET_SPEC
167250837Ssjg        	Sometimes MACHINE isn't enough.
168250837Ssjg
169246149Ssjg        HOST_TARGET
170268437Ssjg		when we build for the pseudo machine 'host'
171246149Ssjg		the object tree uses HOST_TARGET rather than MACHINE.
172246149Ssjg
173246149Ssjg        OBJROOTS a list of the common prefix for all obj dirs it might
174246149Ssjg		end in '/' or '-'.
175246149Ssjg
176246149Ssjg        DPDEPS	names an optional file to which per file dependencies
177246149Ssjg		will be appended.
178246149Ssjg		For example if 'some/path/foo.h' is read from SRCTOP
179246149Ssjg		then 'DPDEPS_some/path/foo.h +=' "RELDIR" is output.
180246149Ssjg		This can allow 'bmake' to learn all the dirs within
181246149Ssjg 		the tree that depend on 'foo.h'
182246149Ssjg
183281812Ssjg	EXCLUDES
184281812Ssjg		A list of paths to ignore.
185281812Ssjg		ccache(1) can otherwise be trouble.
186281812Ssjg
187246149Ssjg        debug	desired debug level
188246149Ssjg
189246149Ssjg        debug_out open file to send debug output to (sys.stderr)
190246149Ssjg
191246149Ssjg        """
192246149Ssjg
193246149Ssjg        self.name = name
194246149Ssjg        self.debug = getv(conf, 'debug', 0)
195246149Ssjg        self.debug_out = getv(conf, 'debug_out', sys.stderr)
196246149Ssjg
197249033Ssjg        self.machine = getv(conf, 'MACHINE', '')
198250837Ssjg        self.machine_arch = getv(conf, 'MACHINE_ARCH', '')
199250837Ssjg        self.target_spec = getv(conf, 'TARGET_SPEC', '')
200249033Ssjg        self.curdir = getv(conf, 'CURDIR')
201249033Ssjg        self.reldir = getv(conf, 'RELDIR')
202249033Ssjg        self.dpdeps = getv(conf, 'DPDEPS')
203253883Ssjg        self.line = 0
204249033Ssjg
205246149Ssjg        if not self.conf:
206246149Ssjg            # some of the steps below we want to do only once
207246149Ssjg            self.conf = conf
208246149Ssjg            self.host_target = getv(conf, 'HOST_TARGET')
209246149Ssjg            for srctop in getv(conf, 'SRCTOPS', []):
210246149Ssjg                if srctop[-1] != '/':
211246149Ssjg                    srctop += '/'
212246149Ssjg                if not srctop in self.srctops:
213246149Ssjg                    self.srctops.append(srctop)
214246149Ssjg                _srctop = os.path.realpath(srctop)
215246149Ssjg                if _srctop[-1] != '/':
216246149Ssjg                    _srctop += '/'
217246149Ssjg                if not _srctop in self.srctops:
218246149Ssjg                    self.srctops.append(_srctop)
219246149Ssjg
220250837Ssjg            trim_list = add_trims(self.machine)
221249033Ssjg            if self.machine == 'host':
222250837Ssjg                trim_list += add_trims(self.host_target)
223250837Ssjg            if self.target_spec:
224250837Ssjg                trim_list += add_trims(self.target_spec)
225249033Ssjg
226246149Ssjg            for objroot in getv(conf, 'OBJROOTS', []):
227249033Ssjg                for e in trim_list:
228249033Ssjg                    if objroot.endswith(e):
229249033Ssjg                        # this is not what we want - fix it
230249033Ssjg                        objroot = objroot[0:-len(e)]
231249033Ssjg                        if e.endswith('/'):
232249033Ssjg                            objroot += '/'
233246149Ssjg                if not objroot in self.objroots:
234246149Ssjg                    self.objroots.append(objroot)
235246149Ssjg                    _objroot = os.path.realpath(objroot)
236246149Ssjg                    if objroot[-1] == '/':
237246149Ssjg                        _objroot += '/'
238246149Ssjg                    if not _objroot in self.objroots:
239246149Ssjg                        self.objroots.append(_objroot)
240246149Ssjg
241249033Ssjg            # we want the longest match
242249033Ssjg            self.srctops.sort(reverse=True)
243249033Ssjg            self.objroots.sort(reverse=True)
244281812Ssjg
245281812Ssjg            self.excludes = getv(conf, 'EXCLUDES', [])
246281812Ssjg
247246149Ssjg            if self.debug:
248261212Ssjg                print("host_target=", self.host_target, file=self.debug_out)
249261212Ssjg                print("srctops=", self.srctops, file=self.debug_out)
250261212Ssjg                print("objroots=", self.objroots, file=self.debug_out)
251281812Ssjg                print("excludes=", self.excludes, file=self.debug_out)
252246149Ssjg
253246149Ssjg            self.dirdep_re = re.compile(r'([^/]+)/(.+)')
254246149Ssjg
255246149Ssjg        if self.dpdeps and not self.reldir:
256246149Ssjg            if self.debug:
257261212Ssjg                print("need reldir:", end=' ', file=self.debug_out)
258246149Ssjg            if self.curdir:
259246149Ssjg                srctop = self.find_top(self.curdir, self.srctops)
260246149Ssjg                if srctop:
261246149Ssjg                    self.reldir = self.curdir.replace(srctop,'')
262246149Ssjg                    if self.debug:
263261212Ssjg                        print(self.reldir, file=self.debug_out)
264246149Ssjg            if not self.reldir:
265246149Ssjg                self.dpdeps = None      # we cannot do it?
266246149Ssjg
267249033Ssjg        self.cwd = os.getcwd()          # make sure this is initialized
268281812Ssjg        self.last_dir = self.cwd
269249033Ssjg
270246149Ssjg        if name:
271253883Ssjg            self.try_parse()
272246149Ssjg
273246149Ssjg    def reset(self):
274246149Ssjg        """reset state if we are being passed meta files from multiple directories."""
275246149Ssjg        self.seen = {}
276246149Ssjg        self.obj_deps = []
277246149Ssjg        self.src_deps = []
278246149Ssjg        self.file_deps = []
279246149Ssjg
280246149Ssjg    def dirdeps(self, sep='\n'):
281246149Ssjg        """return DIRDEPS"""
282246149Ssjg        return sep.strip() + sep.join(self.obj_deps)
283246149Ssjg
284246149Ssjg    def src_dirdeps(self, sep='\n'):
285246149Ssjg        """return SRC_DIRDEPS"""
286246149Ssjg        return sep.strip() + sep.join(self.src_deps)
287246149Ssjg
288246149Ssjg    def file_depends(self, out=None):
289246149Ssjg        """Append DPDEPS_${file} += ${RELDIR}
290246149Ssjg        for each file we saw, to the output file."""
291246149Ssjg        if not self.reldir:
292246149Ssjg            return None
293246149Ssjg        for f in sort_unique(self.file_deps):
294261212Ssjg            print('DPDEPS_%s += %s' % (f, self.reldir), file=out)
295246149Ssjg
296246149Ssjg    def seenit(self, dir):
297246149Ssjg        """rememer that we have seen dir."""
298246149Ssjg        self.seen[dir] = 1
299246149Ssjg
300246149Ssjg    def add(self, list, data, clue=''):
301246149Ssjg        """add data to list if it isn't already there."""
302246149Ssjg        if data not in list:
303246149Ssjg            list.append(data)
304246149Ssjg            if self.debug:
305261212Ssjg                print("%s: %sAdd: %s" % (self.name, clue, data), file=self.debug_out)
306246149Ssjg
307246149Ssjg    def find_top(self, path, list):
308268437Ssjg        """the logical tree may be split across multiple trees"""
309246149Ssjg        for top in list:
310246149Ssjg            if path.startswith(top):
311246149Ssjg                if self.debug > 2:
312261212Ssjg                    print("found in", top, file=self.debug_out)
313246149Ssjg                return top
314246149Ssjg        return None
315246149Ssjg
316246149Ssjg    def find_obj(self, objroot, dir, path, input):
317246149Ssjg        """return path within objroot, taking care of .dirdep files"""
318246149Ssjg        ddep = None
319246149Ssjg        for ddepf in [path + '.dirdep', dir + '/.dirdep']:
320246149Ssjg            if not ddep and os.path.exists(ddepf):
321261212Ssjg                ddep = open(ddepf, 'r').readline().strip('# \n')
322246149Ssjg                if self.debug > 1:
323261212Ssjg                    print("found %s: %s\n" % (ddepf, ddep), file=self.debug_out)
324246149Ssjg                if ddep.endswith(self.machine):
325246149Ssjg                    ddep = ddep[0:-(1+len(self.machine))]
326250837Ssjg                elif self.target_spec and ddep.endswith(self.target_spec):
327250837Ssjg                    ddep = ddep[0:-(1+len(self.target_spec))]
328246149Ssjg
329246149Ssjg        if not ddep:
330246149Ssjg            # no .dirdeps, so remember that we've seen the raw input
331246149Ssjg            self.seenit(input)
332246149Ssjg            self.seenit(dir)
333246149Ssjg            if self.machine == 'none':
334246149Ssjg                if dir.startswith(objroot):
335246149Ssjg                    return dir.replace(objroot,'')
336246149Ssjg                return None
337246149Ssjg            m = self.dirdep_re.match(dir.replace(objroot,''))
338246149Ssjg            if m:
339246149Ssjg                ddep = m.group(2)
340246149Ssjg                dmachine = m.group(1)
341246149Ssjg                if dmachine != self.machine:
342246149Ssjg                    if not (self.machine == 'host' and
343246149Ssjg                            dmachine == self.host_target):
344246149Ssjg                        if self.debug > 2:
345261212Ssjg                            print("adding .%s to %s" % (dmachine, ddep), file=self.debug_out)
346246149Ssjg                        ddep += '.' + dmachine
347246149Ssjg
348246149Ssjg        return ddep
349246149Ssjg
350253883Ssjg    def try_parse(self, name=None, file=None):
351253883Ssjg        """give file and line number causing exception"""
352253883Ssjg        try:
353253883Ssjg            self.parse(name, file)
354253883Ssjg        except:
355253883Ssjg            # give a useful clue
356261212Ssjg            print('{}:{}: '.format(self.name, self.line), end=' ', file=sys.stderr)
357253883Ssjg            raise
358253883Ssjg
359246149Ssjg    def parse(self, name=None, file=None):
360246149Ssjg        """A meta file looks like:
361246149Ssjg
362246149Ssjg	# Meta data file "path"
363246149Ssjg	CMD "command-line"
364246149Ssjg	CWD "cwd"
365246149Ssjg	TARGET "target"
366246149Ssjg	-- command output --
367246149Ssjg	-- filemon acquired metadata --
368246149Ssjg	# buildmon version 3
369246149Ssjg	V 3
370246149Ssjg	C "pid" "cwd"
371246149Ssjg	E "pid" "path"
372281812Ssjg	F "pid" "child"
373246149Ssjg	R "pid" "path"
374246149Ssjg	W "pid" "path"
375246149Ssjg	X "pid" "status"
376281812Ssjg	D "pid" "path"
377281812Ssjg	L "pid" "src" "target"
378281812Ssjg	M "pid" "old" "new"
379281812Ssjg	S "pid" "path"
380281812Ssjg	# Bye bye
381246149Ssjg
382281812Ssjg	We go to some effort to avoid processing a dependency more than once.
383281812Ssjg	Of the above record types only C,E,F,L,R,V and W are of interest.
384246149Ssjg        """
385246149Ssjg
386246149Ssjg        version = 0                     # unknown
387246149Ssjg        if name:
388246149Ssjg            self.name = name;
389246149Ssjg        if file:
390246149Ssjg            f = file
391281812Ssjg            cwd = self.last_dir = self.cwd
392246149Ssjg        else:
393261212Ssjg            f = open(self.name, 'r')
394246149Ssjg        skip = True
395246149Ssjg        pid_cwd = {}
396246149Ssjg        pid_last_dir = {}
397246149Ssjg        last_pid = 0
398246149Ssjg
399253883Ssjg        self.line = 0
400246149Ssjg        if self.curdir:
401246149Ssjg            self.seenit(self.curdir)    # we ignore this
402246149Ssjg
403246149Ssjg        interesting = 'CEFLRV'
404246149Ssjg        for line in f:
405253883Ssjg            self.line += 1
406246149Ssjg            # ignore anything we don't care about
407246149Ssjg            if not line[0] in interesting:
408246149Ssjg                continue
409246149Ssjg            if self.debug > 2:
410261212Ssjg                print("input:", line, end=' ', file=self.debug_out)
411246149Ssjg            w = line.split()
412246149Ssjg
413246149Ssjg            if skip:
414246149Ssjg                if w[0] == 'V':
415246149Ssjg                    skip = False
416246149Ssjg                    version = int(w[1])
417246149Ssjg                    """
418246149Ssjg                    if version < 4:
419246149Ssjg                        # we cannot ignore 'W' records
420246149Ssjg                        # as they may be 'rw'
421246149Ssjg                        interesting += 'W'
422246149Ssjg                    """
423246149Ssjg                elif w[0] == 'CWD':
424281812Ssjg                    self.cwd = cwd = self.last_dir = w[1]
425246149Ssjg                    self.seenit(cwd)    # ignore this
426246149Ssjg                    if self.debug:
427261212Ssjg                        print("%s: CWD=%s" % (self.name, cwd), file=self.debug_out)
428246149Ssjg                continue
429246149Ssjg
430246149Ssjg            pid = int(w[1])
431246149Ssjg            if pid != last_pid:
432246149Ssjg                if last_pid:
433246149Ssjg                    pid_cwd[last_pid] = cwd
434281812Ssjg                    pid_last_dir[last_pid] = self.last_dir
435246149Ssjg                cwd = getv(pid_cwd, pid, self.cwd)
436281812Ssjg                self.last_dir = getv(pid_last_dir, pid, self.cwd)
437246149Ssjg                last_pid = pid
438246149Ssjg
439246149Ssjg            # process operations
440246149Ssjg            if w[0] == 'F':
441246149Ssjg                npid = int(w[2])
442246149Ssjg                pid_cwd[npid] = cwd
443246149Ssjg                pid_last_dir[npid] = cwd
444246149Ssjg                last_pid = npid
445246149Ssjg                continue
446246149Ssjg            elif w[0] == 'C':
447246149Ssjg                cwd = abspath(w[2], cwd, None, self.debug, self.debug_out)
448246149Ssjg                if cwd.endswith('/.'):
449246149Ssjg                    cwd = cwd[0:-2]
450281812Ssjg                self.last_dir = cwd
451246149Ssjg                if self.debug > 1:
452261212Ssjg                    print("cwd=", cwd, file=self.debug_out)
453246149Ssjg                continue
454246149Ssjg
455246149Ssjg            if w[2] in self.seen:
456246149Ssjg                if self.debug > 2:
457261212Ssjg                    print("seen:", w[2], file=self.debug_out)
458246149Ssjg                continue
459246149Ssjg            # file operations
460246149Ssjg            if w[0] in 'ML':
461281812Ssjg                # these are special, tread src as read and
462281812Ssjg                # target as write
463281812Ssjg                self.parse_path(w[1].strip("'"), cwd, 'R', w)
464281812Ssjg                self.parse_path(w[2].strip("'"), cwd, 'W', w)
465281812Ssjg                continue
466281812Ssjg            elif w[0] in 'ERWS':
467246149Ssjg                path = w[2]
468281812Ssjg                self.parse_path(path, cwd, w[0], w)
469281812Ssjg
470281812Ssjg        if not file:
471281812Ssjg            f.close()
472281812Ssjg
473281812Ssjg    def parse_path(self, path, cwd, op=None, w=[]):
474281812Ssjg        """look at a path for the op specified"""
475281812Ssjg
476281812Ssjg        if not op:
477281812Ssjg            op = w[0]
478281812Ssjg
479281812Ssjg        # we are never interested in .dirdep files as dependencies
480281812Ssjg        if path.endswith('.dirdep'):
481281812Ssjg            return
482281812Ssjg        for p in self.excludes:
483281812Ssjg            if p and path.startswith(p):
484246149Ssjg                if self.debug > 2:
485281812Ssjg                    print >> self.debug_out, "exclude:", p, path
486281812Ssjg                return
487281812Ssjg        # we don't want to resolve the last component if it is
488281812Ssjg        # a symlink
489281812Ssjg        path = resolve(path, cwd, self.last_dir, self.debug, self.debug_out)
490281812Ssjg        if not path:
491281812Ssjg            return
492281812Ssjg        dir,base = os.path.split(path)
493281812Ssjg        if dir in self.seen:
494281812Ssjg            if self.debug > 2:
495281812Ssjg                print("seen:", dir, file=self.debug_out)
496281812Ssjg            return
497281812Ssjg        # we can have a path in an objdir which is a link
498281812Ssjg        # to the src dir, we may need to add dependencies for each
499281812Ssjg        rdir = dir
500281812Ssjg        dir = abspath(dir, cwd, self.last_dir, self.debug, self.debug_out)
501281812Ssjg        if rdir == dir or rdir.find('./') > 0:
502281812Ssjg            rdir = None
503281812Ssjg        # now put path back together
504281812Ssjg        path = '/'.join([dir,base])
505281812Ssjg        if self.debug > 1:
506281812Ssjg            print("raw=%s rdir=%s dir=%s path=%s" % (w[2], rdir, dir, path), file=self.debug_out)
507281812Ssjg        if op in 'RWS':
508281812Ssjg            if path in [self.last_dir, cwd, self.cwd, self.curdir]:
509281812Ssjg                if self.debug > 1:
510281812Ssjg                    print("skipping:", path, file=self.debug_out)
511281812Ssjg                return
512281812Ssjg            if os.path.isdir(path):
513281812Ssjg                if op in 'RW':
514281812Ssjg                    self.last_dir = path;
515281812Ssjg                if self.debug > 1:
516281812Ssjg                    print("ldir=", self.last_dir, file=self.debug_out)
517281812Ssjg                return
518246149Ssjg
519281812Ssjg        if op in 'ERW':
520281812Ssjg            # finally, we get down to it
521281812Ssjg            if dir == self.cwd or dir == self.curdir:
522281812Ssjg                return
523281812Ssjg            srctop = self.find_top(path, self.srctops)
524281812Ssjg            if srctop:
525281812Ssjg                if self.dpdeps:
526281812Ssjg                    self.add(self.file_deps, path.replace(srctop,''), 'file')
527281812Ssjg                self.add(self.src_deps, dir.replace(srctop,''), 'src')
528281812Ssjg                self.seenit(w[2])
529281812Ssjg                self.seenit(dir)
530281812Ssjg                if rdir and not rdir.startswith(srctop):
531281812Ssjg                    dir = rdir      # for below
532281812Ssjg                    rdir = None
533281812Ssjg                else:
534281812Ssjg                    return
535281812Ssjg
536281812Ssjg            objroot = None
537281812Ssjg            for dir in [dir,rdir]:
538281812Ssjg                if not dir:
539246149Ssjg                    continue
540281812Ssjg                objroot = self.find_top(dir, self.objroots)
541246149Ssjg                if objroot:
542281812Ssjg                    break
543281812Ssjg            if objroot:
544281812Ssjg                ddep = self.find_obj(objroot, dir, path, w[2])
545281812Ssjg                if ddep:
546281812Ssjg                    self.add(self.obj_deps, ddep, 'obj')
547281812Ssjg            else:
548281812Ssjg                # don't waste time looking again
549281812Ssjg                self.seenit(w[2])
550281812Ssjg                self.seenit(dir)
551246149Ssjg
552246149Ssjg
553246149Ssjgdef main(argv, klass=MetaFile, xopts='', xoptf=None):
554246149Ssjg    """Simple driver for class MetaFile.
555246149Ssjg
556246149Ssjg    Usage:
557281812Ssjg        script [options] [key=value ...] "meta" ...
558246149Ssjg
559246149Ssjg    Options and key=value pairs contribute to the
560246149Ssjg    dictionary passed to MetaFile.
561246149Ssjg
562246149Ssjg    -S "SRCTOP"
563281812Ssjg                add "SRCTOP" to the "SRCTOPS" list.
564246149Ssjg
565246149Ssjg    -C "CURDIR"
566246149Ssjg
567246149Ssjg    -O "OBJROOT"
568281812Ssjg                add "OBJROOT" to the "OBJROOTS" list.
569246149Ssjg
570246149Ssjg    -m "MACHINE"
571246149Ssjg
572250837Ssjg    -a "MACHINE_ARCH"
573250837Ssjg
574246149Ssjg    -H "HOST_TARGET"
575246149Ssjg
576246149Ssjg    -D "DPDEPS"
577246149Ssjg
578281812Ssjg    -d  bumps debug level
579246149Ssjg
580246149Ssjg    """
581246149Ssjg    import getopt
582246149Ssjg
583246149Ssjg    # import Psyco if we can
584246149Ssjg    # it can speed things up quite a bit
585246149Ssjg    have_psyco = 0
586246149Ssjg    try:
587246149Ssjg        import psyco
588246149Ssjg        psyco.full()
589246149Ssjg        have_psyco = 1
590246149Ssjg    except:
591246149Ssjg        pass
592246149Ssjg
593246149Ssjg    conf = {
594246149Ssjg        'SRCTOPS': [],
595246149Ssjg        'OBJROOTS': [],
596281812Ssjg        'EXCLUDES': [],
597246149Ssjg        }
598246149Ssjg
599246149Ssjg    try:
600246149Ssjg        machine = os.environ['MACHINE']
601246149Ssjg        if machine:
602246149Ssjg            conf['MACHINE'] = machine
603250837Ssjg        machine_arch = os.environ['MACHINE_ARCH']
604250837Ssjg        if machine_arch:
605250837Ssjg            conf['MACHINE_ARCH'] = machine_arch
606246149Ssjg        srctop = os.environ['SB_SRC']
607246149Ssjg        if srctop:
608246149Ssjg            conf['SRCTOPS'].append(srctop)
609246149Ssjg        objroot = os.environ['SB_OBJROOT']
610246149Ssjg        if objroot:
611246149Ssjg            conf['OBJROOTS'].append(objroot)
612246149Ssjg    except:
613246149Ssjg        pass
614246149Ssjg
615246149Ssjg    debug = 0
616246149Ssjg    output = True
617246149Ssjg
618281812Ssjg    opts, args = getopt.getopt(argv[1:], 'a:dS:C:O:R:m:D:H:qT:X:' + xopts)
619246149Ssjg    for o, a in opts:
620250837Ssjg        if o == '-a':
621250837Ssjg            conf['MACHINE_ARCH'] = a
622250837Ssjg        elif o == '-d':
623246149Ssjg            debug += 1
624246149Ssjg        elif o == '-q':
625246149Ssjg            output = False
626246149Ssjg        elif o == '-H':
627246149Ssjg            conf['HOST_TARGET'] = a
628246149Ssjg        elif o == '-S':
629246149Ssjg            if a not in conf['SRCTOPS']:
630246149Ssjg                conf['SRCTOPS'].append(a)
631246149Ssjg        elif o == '-C':
632246149Ssjg            conf['CURDIR'] = a
633246149Ssjg        elif o == '-O':
634246149Ssjg            if a not in conf['OBJROOTS']:
635246149Ssjg                conf['OBJROOTS'].append(a)
636246149Ssjg        elif o == '-R':
637246149Ssjg            conf['RELDIR'] = a
638246149Ssjg        elif o == '-D':
639246149Ssjg            conf['DPDEPS'] = a
640246149Ssjg        elif o == '-m':
641246149Ssjg            conf['MACHINE'] = a
642250837Ssjg        elif o == '-T':
643250837Ssjg            conf['TARGET_SPEC'] = a
644281812Ssjg        elif o == '-X':
645281812Ssjg            if a not in conf['EXCLUDES']:
646281812Ssjg                conf['EXCLUDES'].append(a)
647246149Ssjg        elif xoptf:
648246149Ssjg            xoptf(o, a, conf)
649246149Ssjg
650246149Ssjg    conf['debug'] = debug
651246149Ssjg
652246149Ssjg    # get any var=val assignments
653246149Ssjg    eaten = []
654246149Ssjg    for a in args:
655246149Ssjg        if a.find('=') > 0:
656246149Ssjg            k,v = a.split('=')
657246149Ssjg            if k in ['SRCTOP','OBJROOT','SRCTOPS','OBJROOTS']:
658246149Ssjg                if k == 'SRCTOP':
659246149Ssjg                    k = 'SRCTOPS'
660246149Ssjg                elif k == 'OBJROOT':
661246149Ssjg                    k = 'OBJROOTS'
662246149Ssjg                if v not in conf[k]:
663246149Ssjg                    conf[k].append(v)
664246149Ssjg            else:
665246149Ssjg                conf[k] = v
666246149Ssjg            eaten.append(a)
667246149Ssjg            continue
668246149Ssjg        break
669246149Ssjg
670246149Ssjg    for a in eaten:
671246149Ssjg        args.remove(a)
672246149Ssjg
673246149Ssjg    debug_out = getv(conf, 'debug_out', sys.stderr)
674246149Ssjg
675246149Ssjg    if debug:
676261212Ssjg        print("config:", file=debug_out)
677261212Ssjg        print("psyco=", have_psyco, file=debug_out)
678261212Ssjg        for k,v in list(conf.items()):
679261212Ssjg            print("%s=%s" % (k,v), file=debug_out)
680246149Ssjg
681281812Ssjg    m = None
682246149Ssjg    for a in args:
683253883Ssjg        if a.endswith('.meta'):
684281812Ssjg            if not os.path.exists(a):
685281812Ssjg                continue
686253883Ssjg            m = klass(a, conf)
687253883Ssjg        elif a.startswith('@'):
688253883Ssjg            # there can actually multiple files per line
689253883Ssjg            for line in open(a[1:]):
690253883Ssjg                for f in line.strip().split():
691281812Ssjg                    if not os.path.exists(f):
692281812Ssjg                        continue
693253883Ssjg                    m = klass(f, conf)
694246149Ssjg
695281812Ssjg    if output and m:
696261212Ssjg        print(m.dirdeps())
697246149Ssjg
698261212Ssjg        print(m.src_dirdeps('\nsrc:'))
699246149Ssjg
700246149Ssjg        dpdeps = getv(conf, 'DPDEPS')
701246149Ssjg        if dpdeps:
702246149Ssjg            m.file_depends(open(dpdeps, 'wb'))
703246149Ssjg
704246149Ssjg    return m
705246149Ssjg
706246149Ssjgif __name__ == '__main__':
707246149Ssjg    try:
708246149Ssjg        main(sys.argv)
709246149Ssjg    except:
710246149Ssjg        # yes, this goes to stdout
711261212Ssjg        print("ERROR: ", sys.exc_info()[1])
712246149Ssjg        raise
713246149Ssjg
714