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:
40321653Ssjg	$Id: meta2deps.py,v 1.27 2017/05/24 00:04:04 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
93321653Ssjg        if path == '..':
94321653Ssjg            dw = d.split('/')
95321653Ssjg            p = '/'.join(dw[:-1])
96321653Ssjg            if not p:
97321653Ssjg                p = '/'
98321653Ssjg            return p
99246149Ssjg        p = '/'.join([d,path])
100246149Ssjg        if debug > 2:
101261212Ssjg            print("looking for:", p, end=' ', file=debug_out)
102246149Ssjg        if not os.path.exists(p):
103246149Ssjg            if debug > 2:
104261212Ssjg                print("nope", file=debug_out)
105246149Ssjg            p = None
106246149Ssjg            continue
107246149Ssjg        if debug > 2:
108261212Ssjg            print("found:", p, file=debug_out)
109246149Ssjg        return p
110246149Ssjg    return None
111246149Ssjg
112319884Ssjgdef cleanpath(path):
113319884Ssjg    """cleanup path without using realpath(3)"""
114319884Ssjg    if path.startswith('/'):
115319884Ssjg        r = '/'
116319884Ssjg    else:
117319884Ssjg        r = ''
118319884Ssjg    p = []
119319884Ssjg    w = path.split('/')
120319884Ssjg    for d in w:
121319884Ssjg        if not d or d == '.':
122319884Ssjg            continue
123319884Ssjg        if d == '..':
124321653Ssjg            try:
125321653Ssjg                p.pop()
126321653Ssjg                continue
127321653Ssjg            except:
128321653Ssjg                break
129319884Ssjg        p.append(d)
130319884Ssjg
131319884Ssjg    return r + '/'.join(p)
132319884Ssjg
133246149Ssjgdef abspath(path, cwd, last_dir=None, debug=0, debug_out=sys.stderr):
134246149Ssjg    """
135246149Ssjg    Return an absolute path, resolving via cwd or last_dir if needed.
136319884Ssjg    this gets called a lot, so we try to avoid calling realpath.
137246149Ssjg    """
138253883Ssjg    rpath = resolve(path, cwd, last_dir, debug, debug_out)
139253883Ssjg    if rpath:
140253883Ssjg        path = rpath
141281812Ssjg    if (path.find('/') < 0 or
142319884Ssjg        path.find('./') > 0 or
143319884Ssjg        path.endswith('/..')):
144319884Ssjg        path = cleanpath(path)
145246149Ssjg    return path
146246149Ssjg
147246149Ssjgdef sort_unique(list, cmp=None, key=None, reverse=False):
148246149Ssjg    list.sort(cmp, key, reverse)
149246149Ssjg    nl = []
150246149Ssjg    le = None
151246149Ssjg    for e in list:
152246149Ssjg        if e == le:
153246149Ssjg            continue
154319884Ssjg        le = e
155246149Ssjg        nl.append(e)
156246149Ssjg    return nl
157246149Ssjg
158250837Ssjgdef add_trims(x):
159250837Ssjg    return ['/' + x + '/',
160250837Ssjg            '/' + x,
161250837Ssjg            x + '/',
162250837Ssjg            x]
163250837Ssjg
164246149Ssjgclass MetaFile:
165246149Ssjg    """class to parse meta files generated by bmake."""
166246149Ssjg
167246149Ssjg    conf = None
168246149Ssjg    dirdep_re = None
169246149Ssjg    host_target = None
170246149Ssjg    srctops = []
171246149Ssjg    objroots = []
172281812Ssjg    excludes = []
173246149Ssjg    seen = {}
174246149Ssjg    obj_deps = []
175246149Ssjg    src_deps = []
176246149Ssjg    file_deps = []
177246149Ssjg
178246149Ssjg    def __init__(self, name, conf={}):
179246149Ssjg        """if name is set we will parse it now.
180246149Ssjg        conf can have the follwing keys:
181246149Ssjg
182319884Ssjg        SRCTOPS list of tops of the src tree(s).
183246149Ssjg
184319884Ssjg        CURDIR  the src directory 'bmake' was run from.
185246149Ssjg
186319884Ssjg        RELDIR  the relative path from SRCTOP to CURDIR
187246149Ssjg
188319884Ssjg        MACHINE the machine we built for.
189319884Ssjg                set to 'none' if we are not cross-building.
190319884Ssjg                More specifically if machine cannot be deduced from objdirs.
191246149Ssjg
192250837Ssjg        TARGET_SPEC
193319884Ssjg                Sometimes MACHINE isn't enough.
194250837Ssjg
195246149Ssjg        HOST_TARGET
196319884Ssjg                when we build for the pseudo machine 'host'
197319884Ssjg                the object tree uses HOST_TARGET rather than MACHINE.
198246149Ssjg
199246149Ssjg        OBJROOTS a list of the common prefix for all obj dirs it might
200319884Ssjg                end in '/' or '-'.
201246149Ssjg
202319884Ssjg        DPDEPS  names an optional file to which per file dependencies
203319884Ssjg                will be appended.
204319884Ssjg                For example if 'some/path/foo.h' is read from SRCTOP
205319884Ssjg                then 'DPDEPS_some/path/foo.h +=' "RELDIR" is output.
206319884Ssjg                This can allow 'bmake' to learn all the dirs within
207319884Ssjg                the tree that depend on 'foo.h'
208246149Ssjg
209319884Ssjg        EXCLUDES
210319884Ssjg                A list of paths to ignore.
211319884Ssjg                ccache(1) can otherwise be trouble.
212281812Ssjg
213319884Ssjg        debug   desired debug level
214246149Ssjg
215246149Ssjg        debug_out open file to send debug output to (sys.stderr)
216246149Ssjg
217246149Ssjg        """
218246149Ssjg
219246149Ssjg        self.name = name
220246149Ssjg        self.debug = getv(conf, 'debug', 0)
221246149Ssjg        self.debug_out = getv(conf, 'debug_out', sys.stderr)
222246149Ssjg
223249033Ssjg        self.machine = getv(conf, 'MACHINE', '')
224250837Ssjg        self.machine_arch = getv(conf, 'MACHINE_ARCH', '')
225250837Ssjg        self.target_spec = getv(conf, 'TARGET_SPEC', '')
226249033Ssjg        self.curdir = getv(conf, 'CURDIR')
227249033Ssjg        self.reldir = getv(conf, 'RELDIR')
228249033Ssjg        self.dpdeps = getv(conf, 'DPDEPS')
229253883Ssjg        self.line = 0
230249033Ssjg
231246149Ssjg        if not self.conf:
232246149Ssjg            # some of the steps below we want to do only once
233246149Ssjg            self.conf = conf
234246149Ssjg            self.host_target = getv(conf, 'HOST_TARGET')
235246149Ssjg            for srctop in getv(conf, 'SRCTOPS', []):
236246149Ssjg                if srctop[-1] != '/':
237246149Ssjg                    srctop += '/'
238246149Ssjg                if not srctop in self.srctops:
239246149Ssjg                    self.srctops.append(srctop)
240246149Ssjg                _srctop = os.path.realpath(srctop)
241246149Ssjg                if _srctop[-1] != '/':
242246149Ssjg                    _srctop += '/'
243246149Ssjg                if not _srctop in self.srctops:
244246149Ssjg                    self.srctops.append(_srctop)
245246149Ssjg
246250837Ssjg            trim_list = add_trims(self.machine)
247249033Ssjg            if self.machine == 'host':
248250837Ssjg                trim_list += add_trims(self.host_target)
249250837Ssjg            if self.target_spec:
250250837Ssjg                trim_list += add_trims(self.target_spec)
251249033Ssjg
252246149Ssjg            for objroot in getv(conf, 'OBJROOTS', []):
253249033Ssjg                for e in trim_list:
254249033Ssjg                    if objroot.endswith(e):
255249033Ssjg                        # this is not what we want - fix it
256249033Ssjg                        objroot = objroot[0:-len(e)]
257319884Ssjg
258319884Ssjg                if objroot[-1] != '/':
259319884Ssjg                    objroot += '/'
260246149Ssjg                if not objroot in self.objroots:
261246149Ssjg                    self.objroots.append(objroot)
262246149Ssjg                    _objroot = os.path.realpath(objroot)
263246149Ssjg                    if objroot[-1] == '/':
264246149Ssjg                        _objroot += '/'
265246149Ssjg                    if not _objroot in self.objroots:
266246149Ssjg                        self.objroots.append(_objroot)
267246149Ssjg
268249033Ssjg            # we want the longest match
269249033Ssjg            self.srctops.sort(reverse=True)
270249033Ssjg            self.objroots.sort(reverse=True)
271281812Ssjg
272281812Ssjg            self.excludes = getv(conf, 'EXCLUDES', [])
273281812Ssjg
274246149Ssjg            if self.debug:
275261212Ssjg                print("host_target=", self.host_target, file=self.debug_out)
276261212Ssjg                print("srctops=", self.srctops, file=self.debug_out)
277261212Ssjg                print("objroots=", self.objroots, file=self.debug_out)
278281812Ssjg                print("excludes=", self.excludes, file=self.debug_out)
279246149Ssjg
280246149Ssjg            self.dirdep_re = re.compile(r'([^/]+)/(.+)')
281246149Ssjg
282246149Ssjg        if self.dpdeps and not self.reldir:
283246149Ssjg            if self.debug:
284261212Ssjg                print("need reldir:", end=' ', file=self.debug_out)
285246149Ssjg            if self.curdir:
286246149Ssjg                srctop = self.find_top(self.curdir, self.srctops)
287246149Ssjg                if srctop:
288246149Ssjg                    self.reldir = self.curdir.replace(srctop,'')
289246149Ssjg                    if self.debug:
290261212Ssjg                        print(self.reldir, file=self.debug_out)
291246149Ssjg            if not self.reldir:
292246149Ssjg                self.dpdeps = None      # we cannot do it?
293246149Ssjg
294249033Ssjg        self.cwd = os.getcwd()          # make sure this is initialized
295281812Ssjg        self.last_dir = self.cwd
296249033Ssjg
297246149Ssjg        if name:
298253883Ssjg            self.try_parse()
299246149Ssjg
300246149Ssjg    def reset(self):
301246149Ssjg        """reset state if we are being passed meta files from multiple directories."""
302246149Ssjg        self.seen = {}
303246149Ssjg        self.obj_deps = []
304246149Ssjg        self.src_deps = []
305246149Ssjg        self.file_deps = []
306246149Ssjg
307246149Ssjg    def dirdeps(self, sep='\n'):
308246149Ssjg        """return DIRDEPS"""
309246149Ssjg        return sep.strip() + sep.join(self.obj_deps)
310246149Ssjg
311246149Ssjg    def src_dirdeps(self, sep='\n'):
312246149Ssjg        """return SRC_DIRDEPS"""
313246149Ssjg        return sep.strip() + sep.join(self.src_deps)
314246149Ssjg
315246149Ssjg    def file_depends(self, out=None):
316246149Ssjg        """Append DPDEPS_${file} += ${RELDIR}
317246149Ssjg        for each file we saw, to the output file."""
318246149Ssjg        if not self.reldir:
319246149Ssjg            return None
320246149Ssjg        for f in sort_unique(self.file_deps):
321261212Ssjg            print('DPDEPS_%s += %s' % (f, self.reldir), file=out)
322319884Ssjg        # these entries provide for reverse DIRDEPS lookup
323319884Ssjg        for f in self.obj_deps:
324319884Ssjg            print('DEPDIRS_%s += %s' % (f, self.reldir), file=out)
325246149Ssjg
326246149Ssjg    def seenit(self, dir):
327246149Ssjg        """rememer that we have seen dir."""
328246149Ssjg        self.seen[dir] = 1
329246149Ssjg
330246149Ssjg    def add(self, list, data, clue=''):
331246149Ssjg        """add data to list if it isn't already there."""
332246149Ssjg        if data not in list:
333246149Ssjg            list.append(data)
334246149Ssjg            if self.debug:
335261212Ssjg                print("%s: %sAdd: %s" % (self.name, clue, data), file=self.debug_out)
336246149Ssjg
337246149Ssjg    def find_top(self, path, list):
338268437Ssjg        """the logical tree may be split across multiple trees"""
339246149Ssjg        for top in list:
340246149Ssjg            if path.startswith(top):
341246149Ssjg                if self.debug > 2:
342261212Ssjg                    print("found in", top, file=self.debug_out)
343246149Ssjg                return top
344246149Ssjg        return None
345246149Ssjg
346246149Ssjg    def find_obj(self, objroot, dir, path, input):
347246149Ssjg        """return path within objroot, taking care of .dirdep files"""
348246149Ssjg        ddep = None
349246149Ssjg        for ddepf in [path + '.dirdep', dir + '/.dirdep']:
350246149Ssjg            if not ddep and os.path.exists(ddepf):
351261212Ssjg                ddep = open(ddepf, 'r').readline().strip('# \n')
352246149Ssjg                if self.debug > 1:
353261212Ssjg                    print("found %s: %s\n" % (ddepf, ddep), file=self.debug_out)
354246149Ssjg                if ddep.endswith(self.machine):
355246149Ssjg                    ddep = ddep[0:-(1+len(self.machine))]
356250837Ssjg                elif self.target_spec and ddep.endswith(self.target_spec):
357250837Ssjg                    ddep = ddep[0:-(1+len(self.target_spec))]
358246149Ssjg
359246149Ssjg        if not ddep:
360246149Ssjg            # no .dirdeps, so remember that we've seen the raw input
361246149Ssjg            self.seenit(input)
362246149Ssjg            self.seenit(dir)
363246149Ssjg            if self.machine == 'none':
364246149Ssjg                if dir.startswith(objroot):
365246149Ssjg                    return dir.replace(objroot,'')
366246149Ssjg                return None
367246149Ssjg            m = self.dirdep_re.match(dir.replace(objroot,''))
368246149Ssjg            if m:
369246149Ssjg                ddep = m.group(2)
370246149Ssjg                dmachine = m.group(1)
371246149Ssjg                if dmachine != self.machine:
372246149Ssjg                    if not (self.machine == 'host' and
373246149Ssjg                            dmachine == self.host_target):
374246149Ssjg                        if self.debug > 2:
375261212Ssjg                            print("adding .%s to %s" % (dmachine, ddep), file=self.debug_out)
376246149Ssjg                        ddep += '.' + dmachine
377246149Ssjg
378246149Ssjg        return ddep
379246149Ssjg
380253883Ssjg    def try_parse(self, name=None, file=None):
381253883Ssjg        """give file and line number causing exception"""
382253883Ssjg        try:
383253883Ssjg            self.parse(name, file)
384253883Ssjg        except:
385253883Ssjg            # give a useful clue
386261212Ssjg            print('{}:{}: '.format(self.name, self.line), end=' ', file=sys.stderr)
387253883Ssjg            raise
388253883Ssjg
389246149Ssjg    def parse(self, name=None, file=None):
390246149Ssjg        """A meta file looks like:
391246149Ssjg
392319884Ssjg        # Meta data file "path"
393319884Ssjg        CMD "command-line"
394319884Ssjg        CWD "cwd"
395319884Ssjg        TARGET "target"
396319884Ssjg        -- command output --
397319884Ssjg        -- filemon acquired metadata --
398319884Ssjg        # buildmon version 3
399319884Ssjg        V 3
400319884Ssjg        C "pid" "cwd"
401319884Ssjg        E "pid" "path"
402319884Ssjg        F "pid" "child"
403319884Ssjg        R "pid" "path"
404319884Ssjg        W "pid" "path"
405319884Ssjg        X "pid" "status"
406319884Ssjg        D "pid" "path"
407319884Ssjg        L "pid" "src" "target"
408319884Ssjg        M "pid" "old" "new"
409319884Ssjg        S "pid" "path"
410319884Ssjg        # Bye bye
411246149Ssjg
412319884Ssjg        We go to some effort to avoid processing a dependency more than once.
413319884Ssjg        Of the above record types only C,E,F,L,R,V and W are of interest.
414246149Ssjg        """
415246149Ssjg
416246149Ssjg        version = 0                     # unknown
417246149Ssjg        if name:
418246149Ssjg            self.name = name;
419246149Ssjg        if file:
420246149Ssjg            f = file
421281812Ssjg            cwd = self.last_dir = self.cwd
422246149Ssjg        else:
423261212Ssjg            f = open(self.name, 'r')
424246149Ssjg        skip = True
425246149Ssjg        pid_cwd = {}
426246149Ssjg        pid_last_dir = {}
427246149Ssjg        last_pid = 0
428246149Ssjg
429253883Ssjg        self.line = 0
430246149Ssjg        if self.curdir:
431246149Ssjg            self.seenit(self.curdir)    # we ignore this
432246149Ssjg
433246149Ssjg        interesting = 'CEFLRV'
434246149Ssjg        for line in f:
435253883Ssjg            self.line += 1
436246149Ssjg            # ignore anything we don't care about
437246149Ssjg            if not line[0] in interesting:
438246149Ssjg                continue
439246149Ssjg            if self.debug > 2:
440261212Ssjg                print("input:", line, end=' ', file=self.debug_out)
441246149Ssjg            w = line.split()
442246149Ssjg
443246149Ssjg            if skip:
444246149Ssjg                if w[0] == 'V':
445246149Ssjg                    skip = False
446246149Ssjg                    version = int(w[1])
447246149Ssjg                    """
448246149Ssjg                    if version < 4:
449246149Ssjg                        # we cannot ignore 'W' records
450246149Ssjg                        # as they may be 'rw'
451246149Ssjg                        interesting += 'W'
452246149Ssjg                    """
453246149Ssjg                elif w[0] == 'CWD':
454281812Ssjg                    self.cwd = cwd = self.last_dir = w[1]
455246149Ssjg                    self.seenit(cwd)    # ignore this
456246149Ssjg                    if self.debug:
457261212Ssjg                        print("%s: CWD=%s" % (self.name, cwd), file=self.debug_out)
458246149Ssjg                continue
459246149Ssjg
460246149Ssjg            pid = int(w[1])
461246149Ssjg            if pid != last_pid:
462246149Ssjg                if last_pid:
463281812Ssjg                    pid_last_dir[last_pid] = self.last_dir
464246149Ssjg                cwd = getv(pid_cwd, pid, self.cwd)
465281812Ssjg                self.last_dir = getv(pid_last_dir, pid, self.cwd)
466246149Ssjg                last_pid = pid
467246149Ssjg
468246149Ssjg            # process operations
469246149Ssjg            if w[0] == 'F':
470246149Ssjg                npid = int(w[2])
471246149Ssjg                pid_cwd[npid] = cwd
472246149Ssjg                pid_last_dir[npid] = cwd
473246149Ssjg                last_pid = npid
474246149Ssjg                continue
475246149Ssjg            elif w[0] == 'C':
476246149Ssjg                cwd = abspath(w[2], cwd, None, self.debug, self.debug_out)
477246149Ssjg                if cwd.endswith('/.'):
478246149Ssjg                    cwd = cwd[0:-2]
479319884Ssjg                self.last_dir = pid_last_dir[pid] = cwd
480319884Ssjg                pid_cwd[pid] = cwd
481246149Ssjg                if self.debug > 1:
482261212Ssjg                    print("cwd=", cwd, file=self.debug_out)
483246149Ssjg                continue
484246149Ssjg
485246149Ssjg            if w[2] in self.seen:
486246149Ssjg                if self.debug > 2:
487261212Ssjg                    print("seen:", w[2], file=self.debug_out)
488246149Ssjg                continue
489246149Ssjg            # file operations
490246149Ssjg            if w[0] in 'ML':
491281812Ssjg                # these are special, tread src as read and
492281812Ssjg                # target as write
493281812Ssjg                self.parse_path(w[1].strip("'"), cwd, 'R', w)
494281812Ssjg                self.parse_path(w[2].strip("'"), cwd, 'W', w)
495281812Ssjg                continue
496281812Ssjg            elif w[0] in 'ERWS':
497246149Ssjg                path = w[2]
498281812Ssjg                self.parse_path(path, cwd, w[0], w)
499281812Ssjg
500281812Ssjg        if not file:
501281812Ssjg            f.close()
502281812Ssjg
503319884Ssjg    def is_src(self, base, dir, rdir):
504319884Ssjg        """is base in srctop"""
505319884Ssjg        for dir in [dir,rdir]:
506319884Ssjg            if not dir:
507319884Ssjg                continue
508319884Ssjg            path = '/'.join([dir,base])
509319884Ssjg            srctop = self.find_top(path, self.srctops)
510319884Ssjg            if srctop:
511319884Ssjg                if self.dpdeps:
512319884Ssjg                    self.add(self.file_deps, path.replace(srctop,''), 'file')
513319884Ssjg                self.add(self.src_deps, dir.replace(srctop,''), 'src')
514319884Ssjg                self.seenit(dir)
515319884Ssjg                return True
516319884Ssjg        return False
517319884Ssjg
518281812Ssjg    def parse_path(self, path, cwd, op=None, w=[]):
519281812Ssjg        """look at a path for the op specified"""
520281812Ssjg
521281812Ssjg        if not op:
522281812Ssjg            op = w[0]
523281812Ssjg
524281812Ssjg        # we are never interested in .dirdep files as dependencies
525281812Ssjg        if path.endswith('.dirdep'):
526281812Ssjg            return
527281812Ssjg        for p in self.excludes:
528281812Ssjg            if p and path.startswith(p):
529246149Ssjg                if self.debug > 2:
530300313Ssjg                    print("exclude:", p, path, file=self.debug_out)
531281812Ssjg                return
532281812Ssjg        # we don't want to resolve the last component if it is
533281812Ssjg        # a symlink
534281812Ssjg        path = resolve(path, cwd, self.last_dir, self.debug, self.debug_out)
535281812Ssjg        if not path:
536281812Ssjg            return
537281812Ssjg        dir,base = os.path.split(path)
538281812Ssjg        if dir in self.seen:
539281812Ssjg            if self.debug > 2:
540281812Ssjg                print("seen:", dir, file=self.debug_out)
541281812Ssjg            return
542281812Ssjg        # we can have a path in an objdir which is a link
543281812Ssjg        # to the src dir, we may need to add dependencies for each
544281812Ssjg        rdir = dir
545281812Ssjg        dir = abspath(dir, cwd, self.last_dir, self.debug, self.debug_out)
546319884Ssjg        rdir = os.path.realpath(dir)
547319884Ssjg        if rdir == dir:
548281812Ssjg            rdir = None
549281812Ssjg        # now put path back together
550281812Ssjg        path = '/'.join([dir,base])
551281812Ssjg        if self.debug > 1:
552281812Ssjg            print("raw=%s rdir=%s dir=%s path=%s" % (w[2], rdir, dir, path), file=self.debug_out)
553281812Ssjg        if op in 'RWS':
554281812Ssjg            if path in [self.last_dir, cwd, self.cwd, self.curdir]:
555281812Ssjg                if self.debug > 1:
556281812Ssjg                    print("skipping:", path, file=self.debug_out)
557281812Ssjg                return
558281812Ssjg            if os.path.isdir(path):
559281812Ssjg                if op in 'RW':
560281812Ssjg                    self.last_dir = path;
561281812Ssjg                if self.debug > 1:
562281812Ssjg                    print("ldir=", self.last_dir, file=self.debug_out)
563281812Ssjg                return
564246149Ssjg
565281812Ssjg        if op in 'ERW':
566281812Ssjg            # finally, we get down to it
567281812Ssjg            if dir == self.cwd or dir == self.curdir:
568281812Ssjg                return
569319884Ssjg            if self.is_src(base, dir, rdir):
570281812Ssjg                self.seenit(w[2])
571319884Ssjg                if not rdir:
572281812Ssjg                    return
573281812Ssjg
574281812Ssjg            objroot = None
575281812Ssjg            for dir in [dir,rdir]:
576281812Ssjg                if not dir:
577246149Ssjg                    continue
578281812Ssjg                objroot = self.find_top(dir, self.objroots)
579246149Ssjg                if objroot:
580281812Ssjg                    break
581281812Ssjg            if objroot:
582281812Ssjg                ddep = self.find_obj(objroot, dir, path, w[2])
583281812Ssjg                if ddep:
584281812Ssjg                    self.add(self.obj_deps, ddep, 'obj')
585319884Ssjg                    if self.dpdeps and objroot.endswith('/stage/'):
586319884Ssjg                        sp = '/'.join(path.replace(objroot,'').split('/')[1:])
587319884Ssjg                        self.add(self.file_deps, sp, 'file')
588281812Ssjg            else:
589281812Ssjg                # don't waste time looking again
590281812Ssjg                self.seenit(w[2])
591281812Ssjg                self.seenit(dir)
592246149Ssjg
593246149Ssjg
594246149Ssjgdef main(argv, klass=MetaFile, xopts='', xoptf=None):
595246149Ssjg    """Simple driver for class MetaFile.
596246149Ssjg
597246149Ssjg    Usage:
598281812Ssjg        script [options] [key=value ...] "meta" ...
599246149Ssjg
600246149Ssjg    Options and key=value pairs contribute to the
601246149Ssjg    dictionary passed to MetaFile.
602246149Ssjg
603246149Ssjg    -S "SRCTOP"
604281812Ssjg                add "SRCTOP" to the "SRCTOPS" list.
605246149Ssjg
606246149Ssjg    -C "CURDIR"
607246149Ssjg
608246149Ssjg    -O "OBJROOT"
609281812Ssjg                add "OBJROOT" to the "OBJROOTS" list.
610246149Ssjg
611246149Ssjg    -m "MACHINE"
612246149Ssjg
613250837Ssjg    -a "MACHINE_ARCH"
614250837Ssjg
615246149Ssjg    -H "HOST_TARGET"
616246149Ssjg
617246149Ssjg    -D "DPDEPS"
618246149Ssjg
619281812Ssjg    -d  bumps debug level
620246149Ssjg
621246149Ssjg    """
622246149Ssjg    import getopt
623246149Ssjg
624246149Ssjg    # import Psyco if we can
625246149Ssjg    # it can speed things up quite a bit
626246149Ssjg    have_psyco = 0
627246149Ssjg    try:
628246149Ssjg        import psyco
629246149Ssjg        psyco.full()
630246149Ssjg        have_psyco = 1
631246149Ssjg    except:
632246149Ssjg        pass
633246149Ssjg
634246149Ssjg    conf = {
635246149Ssjg        'SRCTOPS': [],
636246149Ssjg        'OBJROOTS': [],
637281812Ssjg        'EXCLUDES': [],
638246149Ssjg        }
639246149Ssjg
640246149Ssjg    try:
641246149Ssjg        machine = os.environ['MACHINE']
642246149Ssjg        if machine:
643246149Ssjg            conf['MACHINE'] = machine
644250837Ssjg        machine_arch = os.environ['MACHINE_ARCH']
645250837Ssjg        if machine_arch:
646250837Ssjg            conf['MACHINE_ARCH'] = machine_arch
647246149Ssjg        srctop = os.environ['SB_SRC']
648246149Ssjg        if srctop:
649246149Ssjg            conf['SRCTOPS'].append(srctop)
650246149Ssjg        objroot = os.environ['SB_OBJROOT']
651246149Ssjg        if objroot:
652246149Ssjg            conf['OBJROOTS'].append(objroot)
653246149Ssjg    except:
654246149Ssjg        pass
655246149Ssjg
656246149Ssjg    debug = 0
657246149Ssjg    output = True
658246149Ssjg
659281812Ssjg    opts, args = getopt.getopt(argv[1:], 'a:dS:C:O:R:m:D:H:qT:X:' + xopts)
660246149Ssjg    for o, a in opts:
661250837Ssjg        if o == '-a':
662250837Ssjg            conf['MACHINE_ARCH'] = a
663250837Ssjg        elif o == '-d':
664246149Ssjg            debug += 1
665246149Ssjg        elif o == '-q':
666246149Ssjg            output = False
667246149Ssjg        elif o == '-H':
668246149Ssjg            conf['HOST_TARGET'] = a
669246149Ssjg        elif o == '-S':
670246149Ssjg            if a not in conf['SRCTOPS']:
671246149Ssjg                conf['SRCTOPS'].append(a)
672246149Ssjg        elif o == '-C':
673246149Ssjg            conf['CURDIR'] = a
674246149Ssjg        elif o == '-O':
675246149Ssjg            if a not in conf['OBJROOTS']:
676246149Ssjg                conf['OBJROOTS'].append(a)
677246149Ssjg        elif o == '-R':
678246149Ssjg            conf['RELDIR'] = a
679246149Ssjg        elif o == '-D':
680246149Ssjg            conf['DPDEPS'] = a
681246149Ssjg        elif o == '-m':
682246149Ssjg            conf['MACHINE'] = a
683250837Ssjg        elif o == '-T':
684250837Ssjg            conf['TARGET_SPEC'] = a
685281812Ssjg        elif o == '-X':
686281812Ssjg            if a not in conf['EXCLUDES']:
687281812Ssjg                conf['EXCLUDES'].append(a)
688246149Ssjg        elif xoptf:
689246149Ssjg            xoptf(o, a, conf)
690246149Ssjg
691246149Ssjg    conf['debug'] = debug
692246149Ssjg
693246149Ssjg    # get any var=val assignments
694246149Ssjg    eaten = []
695246149Ssjg    for a in args:
696246149Ssjg        if a.find('=') > 0:
697246149Ssjg            k,v = a.split('=')
698246149Ssjg            if k in ['SRCTOP','OBJROOT','SRCTOPS','OBJROOTS']:
699246149Ssjg                if k == 'SRCTOP':
700246149Ssjg                    k = 'SRCTOPS'
701246149Ssjg                elif k == 'OBJROOT':
702246149Ssjg                    k = 'OBJROOTS'
703246149Ssjg                if v not in conf[k]:
704246149Ssjg                    conf[k].append(v)
705246149Ssjg            else:
706246149Ssjg                conf[k] = v
707246149Ssjg            eaten.append(a)
708246149Ssjg            continue
709246149Ssjg        break
710246149Ssjg
711246149Ssjg    for a in eaten:
712246149Ssjg        args.remove(a)
713246149Ssjg
714246149Ssjg    debug_out = getv(conf, 'debug_out', sys.stderr)
715246149Ssjg
716246149Ssjg    if debug:
717261212Ssjg        print("config:", file=debug_out)
718261212Ssjg        print("psyco=", have_psyco, file=debug_out)
719261212Ssjg        for k,v in list(conf.items()):
720261212Ssjg            print("%s=%s" % (k,v), file=debug_out)
721246149Ssjg
722281812Ssjg    m = None
723246149Ssjg    for a in args:
724253883Ssjg        if a.endswith('.meta'):
725281812Ssjg            if not os.path.exists(a):
726281812Ssjg                continue
727253883Ssjg            m = klass(a, conf)
728253883Ssjg        elif a.startswith('@'):
729253883Ssjg            # there can actually multiple files per line
730253883Ssjg            for line in open(a[1:]):
731253883Ssjg                for f in line.strip().split():
732281812Ssjg                    if not os.path.exists(f):
733281812Ssjg                        continue
734253883Ssjg                    m = klass(f, conf)
735246149Ssjg
736281812Ssjg    if output and m:
737261212Ssjg        print(m.dirdeps())
738246149Ssjg
739261212Ssjg        print(m.src_dirdeps('\nsrc:'))
740246149Ssjg
741246149Ssjg        dpdeps = getv(conf, 'DPDEPS')
742246149Ssjg        if dpdeps:
743246149Ssjg            m.file_depends(open(dpdeps, 'wb'))
744246149Ssjg
745246149Ssjg    return m
746246149Ssjg
747246149Ssjgif __name__ == '__main__':
748246149Ssjg    try:
749246149Ssjg        main(sys.argv)
750246149Ssjg    except:
751246149Ssjg        # yes, this goes to stdout
752261212Ssjg        print("ERROR: ", sys.exc_info()[1])
753246149Ssjg        raise
754246149Ssjg
755