1187767Sluigi#!/usr/bin/env python 2187767Sluigi 3187767Sluigifrom __future__ import print_function 4187767Sluigi 5187767Sluigi""" 6187767SluigiThis script parses each "meta" file and extracts the 7187767Sluigiinformation needed to deduce build and src dependencies. 8187767Sluigi 9187767SluigiIt works much the same as the original shell script, but is 10187767Sluigi*much* more efficient. 11187767Sluigi 12187767SluigiThe parsing work is handled by the class MetaFile. 13187767SluigiWe only pay attention to a subset of the information in the 14187767Sluigi"meta" files. Specifically: 15187767Sluigi 16187767Sluigi'CWD' to initialize our notion. 17187767Sluigi 18187767Sluigi'C' to track chdir(2) on a per process basis 19187767Sluigi 20187767Sluigi'R' files read are what we really care about. 21187767Sluigi directories read, provide a clue to resolving 22187767Sluigi subsequent relative paths. That is if we cannot find 23187767Sluigi them relative to 'cwd', we check relative to the last 24187767Sluigi dir read. 25187767Sluigi 26187767Sluigi'W' files opened for write or read-write, 27187767Sluigi for filemon V3 and earlier. 28187767Sluigi 29187767Sluigi'E' files executed. 30187767Sluigi 31187767Sluigi'L' files linked 32187767Sluigi 33187767Sluigi'V' the filemon version, this record is used as a clue 34187767Sluigi that we have reached the interesting bit. 35187767Sluigi 36187767Sluigi""" 37187767Sluigi 38187767Sluigi""" 39187767SluigiSPDX-License-Identifier: BSD-2-Clause 40187767Sluigi 41187767SluigiRCSid: 42187767Sluigi $Id: meta2deps.py,v 1.47 2024/02/17 17:26:57 sjg Exp $ 43187767Sluigi 44187767Sluigi Copyright (c) 2011-2020, Simon J. Gerraty 45187767Sluigi Copyright (c) 2011-2017, Juniper Networks, Inc. 46187767Sluigi All rights reserved. 47187767Sluigi 48187767Sluigi Redistribution and use in source and binary forms, with or without 49187767Sluigi modification, are permitted provided that the following conditions 50187767Sluigi are met: 51187767Sluigi 1. Redistributions of source code must retain the above copyright 52187767Sluigi notice, this list of conditions and the following disclaimer. 53187767Sluigi 2. Redistributions in binary form must reproduce the above copyright 54187767Sluigi notice, this list of conditions and the following disclaimer in the 55187767Sluigi documentation and/or other materials provided with the distribution. 56187767Sluigi 57187767Sluigi THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 58187767Sluigi "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 59187767Sluigi LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 60187767Sluigi A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 61187767Sluigi OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 62187767Sluigi SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 63187767Sluigi LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 64187767Sluigi DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 65187767Sluigi THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 66187767Sluigi (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 67187767Sluigi OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 68187767Sluigi 69187767Sluigi""" 70187767Sluigi 71187767Sluigiimport os 72187767Sluigiimport re 73187767Sluigiimport sys 74187769Sluigiimport stat 75187769Sluigi 76187769Sluigidef resolve(path, cwd, last_dir=None, debug=0, debug_out=sys.stderr): 77187769Sluigi """ 78187769Sluigi Return an absolute path, resolving via cwd or last_dir if needed. 79187769Sluigi 80187769Sluigi Cleanup any leading ``./`` and trailing ``/.`` 81187769Sluigi """ 82187769Sluigi while path.endswith('/.'): 83187769Sluigi path = path[0:-2] 84187769Sluigi if len(path) > 0 and path[0] == '/': 85187769Sluigi if os.path.exists(path): 86187769Sluigi return path 87187769Sluigi if debug > 2: 88187769Sluigi print("skipping non-existent:", path, file=debug_out) 89187769Sluigi return None 90187769Sluigi if path == '.': 91187769Sluigi return cwd 92187769Sluigi if path.startswith('./'): 93187769Sluigi while path.startswith('./'): 94187769Sluigi path = path[1:] 95187769Sluigi return cwd + path 96187769Sluigi if last_dir == cwd: 97187769Sluigi last_dir = None 98187769Sluigi for d in [last_dir, cwd]: 99187769Sluigi if not d: 100187769Sluigi continue 101187769Sluigi if path == '..': 102187769Sluigi dw = d.split('/') 103187769Sluigi p = '/'.join(dw[:-1]) 104187769Sluigi if not p: 105187769Sluigi p = '/' 106187769Sluigi return p 107187769Sluigi p = '/'.join([d,path]) 108187769Sluigi if debug > 2: 109187769Sluigi print("looking for:", p, end=' ', file=debug_out) 110187769Sluigi if not os.path.exists(p): 111187769Sluigi if debug > 2: 112187769Sluigi print("nope", file=debug_out) 113187769Sluigi p = None 114187769Sluigi continue 115187769Sluigi if debug > 2: 116187769Sluigi print("found:", p, file=debug_out) 117187769Sluigi return p 118187769Sluigi return None 119187769Sluigi 120187769Sluigidef cleanpath(path): 121187769Sluigi """cleanup path without using realpath(3)""" 122187769Sluigi if path.startswith('/'): 123187769Sluigi r = '/' 124187769Sluigi else: 125187769Sluigi r = '' 126187769Sluigi p = [] 127187769Sluigi w = path.split('/') 128187769Sluigi for d in w: 129187769Sluigi if not d or d == '.': 130187769Sluigi continue 131187769Sluigi if d == '..': 132187769Sluigi try: 133187769Sluigi p.pop() 134187769Sluigi continue 135187769Sluigi except: 136187769Sluigi break 137187769Sluigi p.append(d) 138187769Sluigi 139187769Sluigi return r + '/'.join(p) 140187769Sluigi 141187769Sluigidef abspath(path, cwd, last_dir=None, debug=0, debug_out=sys.stderr): 142187769Sluigi """ 143187769Sluigi Return an absolute path, resolving via cwd or last_dir if needed. 144187769Sluigi this gets called a lot, so we try to avoid calling realpath. 145187769Sluigi """ 146187769Sluigi rpath = resolve(path, cwd, last_dir, debug, debug_out) 147187769Sluigi if rpath: 148187769Sluigi path = rpath 149187769Sluigi elif len(path) > 0 and path[0] == '/': 150187769Sluigi return None 151187769Sluigi if (path.find('/') < 0 or 152187769Sluigi path.find('./') > 0 or 153187769Sluigi path.find('/../') > 0 or 154187769Sluigi path.endswith('/..')): 155187769Sluigi path = cleanpath(path) 156187769Sluigi return path 157187769Sluigi 158187769Sluigidef sort_unique(list, cmp=None, key=None, reverse=False): 159187769Sluigi if sys.version_info[0] == 2: 160187769Sluigi list.sort(cmp, key, reverse) 161187769Sluigi else: 162187769Sluigi list.sort(reverse=reverse) 163187769Sluigi nl = [] 164187769Sluigi le = None 165187769Sluigi for e in list: 166187769Sluigi if e == le: 167187769Sluigi continue 168187769Sluigi le = e 169187769Sluigi nl.append(e) 170187769Sluigi return nl 171187769Sluigi 172187769Sluigidef add_trims(x): 173187769Sluigi return ['/' + x + '/', 174187769Sluigi '/' + x, 175187769Sluigi x + '/', 176187769Sluigi x] 177187769Sluigi 178187769Sluigidef target_spec_exts(target_spec): 179187769Sluigi """return a list of dirdep extensions that could match target_spec""" 180187769Sluigi 181187769Sluigi if target_spec.find(',') < 0: 182187769Sluigi return ['.'+target_spec] 183187769Sluigi w = target_spec.split(',') 184187769Sluigi n = len(w) 185187769Sluigi e = [] 186187769Sluigi while n > 0: 187187767Sluigi e.append('.'+','.join(w[0:n])) 188187767Sluigi n -= 1 189187767Sluigi return e 190187767Sluigi 191187767Sluigiclass MetaFile: 192187767Sluigi """class to parse meta files generated by bmake.""" 193187787Sluigi 194187787Sluigi conf = None 195187767Sluigi dirdep_re = None 196187767Sluigi host_target = None 197187767Sluigi srctops = [] 198187767Sluigi objroots = [] 199187770Sluigi excludes = [] 200187767Sluigi seen = {} 201187769Sluigi obj_deps = [] 202187767Sluigi src_deps = [] 203187770Sluigi file_deps = [] 204187769Sluigi 205187770Sluigi def __init__(self, name, conf={}): 206187770Sluigi """if name is set we will parse it now. 207187769Sluigi conf can have the follwing keys: 208187769Sluigi 209187769Sluigi SRCTOPS list of tops of the src tree(s). 210187769Sluigi 211187770Sluigi CURDIR the src directory 'bmake' was run from. 212187769Sluigi 213187819Sluigi RELDIR the relative path from SRCTOP to CURDIR 214187819Sluigi 215187819Sluigi MACHINE the machine we built for. 216187819Sluigi set to 'none' if we are not cross-building. 217187819Sluigi More specifically if machine cannot be deduced from objdirs. 218187819Sluigi 219187819Sluigi TARGET_SPEC 220187819Sluigi Sometimes MACHINE isn't enough. 221187819Sluigi 222187983Sluigi HOST_TARGET 223187819Sluigi when we build for the pseudo machine 'host' 224187819Sluigi the object tree uses HOST_TARGET rather than MACHINE. 225187819Sluigi 226187769Sluigi OBJROOTS a list of the common prefix for all obj dirs it might 227187767Sluigi end in '/' or '-'. 228187767Sluigi 229187767Sluigi DPDEPS names an optional file to which per file dependencies 230187767Sluigi will be appended. 231187767Sluigi For example if 'some/path/foo.h' is read from SRCTOP 232187767Sluigi then 'DPDEPS_some/path/foo.h +=' "RELDIR" is output. 233187767Sluigi This can allow 'bmake' to learn all the dirs within 234187770Sluigi the tree that depend on 'foo.h' 235187767Sluigi 236187767Sluigi EXCLUDES 237187767Sluigi A list of paths to ignore. 238187767Sluigi ccache(1) can otherwise be trouble. 239187767Sluigi 240187767Sluigi debug desired debug level 241187767Sluigi 242187767Sluigi debug_out open file to send debug output to (sys.stderr) 243187767Sluigi 244187767Sluigi """ 245187767Sluigi 246187767Sluigi self.name = name 247187983Sluigi self.debug = conf.get('debug', 0) 248187983Sluigi self.debug_out = conf.get('debug_out', sys.stderr) 249187983Sluigi 250187983Sluigi self.machine = conf.get('MACHINE', '') 251187983Sluigi self.machine_arch = conf.get('MACHINE_ARCH', '') 252187983Sluigi self.target_spec = conf.get('TARGET_SPEC', self.machine) 253187770Sluigi self.exts = target_spec_exts(self.target_spec) 254187769Sluigi self.curdir = conf.get('CURDIR') 255187769Sluigi self.reldir = conf.get('RELDIR') 256187769Sluigi self.dpdeps = conf.get('DPDEPS') 257187770Sluigi self.pids = {} 258187770Sluigi self.line = 0 259187819Sluigi 260187819Sluigi if not self.conf: 261187819Sluigi # some of the steps below we want to do only once 262187819Sluigi self.conf = conf 263187770Sluigi self.host_target = conf.get('HOST_TARGET') 264187819Sluigi for srctop in conf.get('SRCTOPS', []): 265187819Sluigi if srctop[-1] != '/': 266187770Sluigi srctop += '/' 267187819Sluigi if not srctop in self.srctops: 268187770Sluigi self.srctops.append(srctop) 269187819Sluigi _srctop = os.path.realpath(srctop) 270187819Sluigi if _srctop[-1] != '/': 271 _srctop += '/' 272 if not _srctop in self.srctops: 273 self.srctops.append(_srctop) 274 275 trim_list = add_trims(self.machine) 276 if self.machine == 'host': 277 trim_list += add_trims(self.host_target) 278 if self.target_spec != self.machine: 279 trim_list += add_trims(self.target_spec) 280 281 for objroot in conf.get('OBJROOTS', []): 282 for e in trim_list: 283 if objroot.endswith(e): 284 # this is not what we want - fix it 285 objroot = objroot[0:-len(e)] 286 287 if objroot[-1] != '/': 288 objroot += '/' 289 if not objroot in self.objroots: 290 self.objroots.append(objroot) 291 _objroot = os.path.realpath(objroot) 292 if objroot[-1] == '/': 293 _objroot += '/' 294 if not _objroot in self.objroots: 295 self.objroots.append(_objroot) 296 297 # we want the longest match 298 self.srctops.sort(reverse=True) 299 self.objroots.sort(reverse=True) 300 301 self.excludes = conf.get('EXCLUDES', []) 302 303 if self.debug: 304 print("host_target=", self.host_target, file=self.debug_out) 305 print("srctops=", self.srctops, file=self.debug_out) 306 print("objroots=", self.objroots, file=self.debug_out) 307 print("excludes=", self.excludes, file=self.debug_out) 308 print("ext_list=", self.exts, file=self.debug_out) 309 310 self.dirdep_re = re.compile(r'([^/]+)/(.+)') 311 312 if self.dpdeps and not self.reldir: 313 if self.debug: 314 print("need reldir:", end=' ', file=self.debug_out) 315 if self.curdir: 316 srctop = self.find_top(self.curdir, self.srctops) 317 if srctop: 318 self.reldir = self.curdir.replace(srctop,'') 319 if self.debug: 320 print(self.reldir, file=self.debug_out) 321 if not self.reldir: 322 self.dpdeps = None # we cannot do it? 323 324 self.cwd = os.getcwd() # make sure this is initialized 325 self.last_dir = self.cwd 326 327 if name: 328 self.try_parse() 329 330 def reset(self): 331 """reset state if we are being passed meta files from multiple directories.""" 332 self.seen = {} 333 self.obj_deps = [] 334 self.src_deps = [] 335 self.file_deps = [] 336 337 def dirdeps(self, sep='\n'): 338 """return DIRDEPS""" 339 return sep.strip() + sep.join(self.obj_deps) 340 341 def src_dirdeps(self, sep='\n'): 342 """return SRC_DIRDEPS""" 343 return sep.strip() + sep.join(self.src_deps) 344 345 def file_depends(self, out=None): 346 """Append DPDEPS_${file} += ${RELDIR} 347 for each file we saw, to the output file.""" 348 if not self.reldir: 349 return None 350 for f in sort_unique(self.file_deps): 351 print('DPDEPS_%s += %s' % (f, self.reldir), file=out) 352 # these entries provide for reverse DIRDEPS lookup 353 for f in self.obj_deps: 354 print('DEPDIRS_%s += %s' % (f, self.reldir), file=out) 355 356 def seenit(self, dir): 357 """rememer that we have seen dir.""" 358 self.seen[dir] = 1 359 360 def add(self, list, data, clue=''): 361 """add data to list if it isn't already there.""" 362 if data not in list: 363 list.append(data) 364 if self.debug: 365 print("%s: %sAdd: %s" % (self.name, clue, data), file=self.debug_out) 366 367 def find_top(self, path, list): 368 """the logical tree may be split across multiple trees""" 369 for top in list: 370 if path.startswith(top): 371 if self.debug > 2: 372 print("found in", top, file=self.debug_out) 373 return top 374 return None 375 376 def find_obj(self, objroot, dir, path, input): 377 """return path within objroot, taking care of .dirdep files""" 378 ddep = None 379 for ddepf in [path + '.dirdep', dir + '/.dirdep']: 380 if not ddep and os.path.exists(ddepf): 381 ddep = open(ddepf, 'r').readline().strip('# \n') 382 if self.debug > 1: 383 print("found %s: %s\n" % (ddepf, ddep), file=self.debug_out) 384 for e in self.exts: 385 if ddep.endswith(e): 386 ddep = ddep[0:-len(e)] 387 break 388 389 if not ddep: 390 # no .dirdeps, so remember that we've seen the raw input 391 self.seenit(input) 392 self.seenit(dir) 393 if self.machine == 'none': 394 if dir.startswith(objroot): 395 return dir.replace(objroot,'') 396 return None 397 m = self.dirdep_re.match(dir.replace(objroot,'')) 398 if m: 399 ddep = m.group(2) 400 dmachine = m.group(1) 401 if dmachine != self.machine: 402 if not (self.machine == 'host' and 403 dmachine == self.host_target): 404 if self.debug > 2: 405 print("adding .%s to %s" % (dmachine, ddep), file=self.debug_out) 406 ddep += '.' + dmachine 407 408 return ddep 409 410 def try_parse(self, name=None, file=None): 411 """give file and line number causing exception""" 412 try: 413 self.parse(name, file) 414 except: 415 # give a useful clue 416 print('{}:{}: '.format(self.name, self.line), end=' ', file=sys.stderr) 417 raise 418 419 def parse(self, name=None, file=None): 420 """A meta file looks like: 421 422 # Meta data file "path" 423 CMD "command-line" 424 CWD "cwd" 425 TARGET "target" 426 -- command output -- 427 -- filemon acquired metadata -- 428 # buildmon version 3 429 V 3 430 C "pid" "cwd" 431 E "pid" "path" 432 F "pid" "child" 433 R "pid" "path" 434 W "pid" "path" 435 X "pid" "status" 436 D "pid" "path" 437 L "pid" "src" "target" 438 M "pid" "old" "new" 439 S "pid" "path" 440 # Bye bye 441 442 We go to some effort to avoid processing a dependency more than once. 443 Of the above record types only C,E,F,L,R,V and W are of interest. 444 """ 445 446 version = 0 # unknown 447 if name: 448 self.name = name; 449 if file: 450 f = file 451 cwd = self.last_dir = self.cwd 452 else: 453 f = open(self.name, 'r') 454 skip = True 455 pid_cwd = {} 456 pid_last_dir = {} 457 last_pid = 0 458 eof_token = False 459 460 self.line = 0 461 if self.curdir: 462 self.seenit(self.curdir) # we ignore this 463 464 interesting = '#CEFLRVX' 465 for line in f: 466 self.line += 1 467 # ignore anything we don't care about 468 if not line[0] in interesting: 469 continue 470 if self.debug > 2: 471 print("input:", line, end=' ', file=self.debug_out) 472 w = line.split() 473 474 if skip: 475 if w[0] == 'V': 476 skip = False 477 version = int(w[1]) 478 """ 479 if version < 4: 480 # we cannot ignore 'W' records 481 # as they may be 'rw' 482 interesting += 'W' 483 """ 484 elif w[0] == 'CWD': 485 self.cwd = cwd = self.last_dir = w[1] 486 self.seenit(cwd) # ignore this 487 if self.debug: 488 print("%s: CWD=%s" % (self.name, cwd), file=self.debug_out) 489 continue 490 491 if w[0] == '#': 492 # check the file has not been truncated 493 if line.find('Bye') > 0: 494 eof_token = True 495 continue 496 497 pid = int(w[1]) 498 if pid != last_pid: 499 if last_pid: 500 pid_last_dir[last_pid] = self.last_dir 501 cwd = pid_cwd.get(pid, self.cwd) 502 self.last_dir = pid_last_dir.get(pid, self.cwd) 503 last_pid = pid 504 505 # process operations 506 if w[0] == 'F': 507 npid = int(w[2]) 508 pid_cwd[npid] = cwd 509 pid_last_dir[npid] = cwd 510 last_pid = npid 511 continue 512 elif w[0] == 'C': 513 cwd = abspath(w[2], cwd, None, self.debug, self.debug_out) 514 if not cwd: 515 cwd = w[2] 516 if self.debug > 1: 517 print("missing cwd=", cwd, file=self.debug_out) 518 if cwd.endswith('/.'): 519 cwd = cwd[0:-2] 520 self.last_dir = pid_last_dir[pid] = cwd 521 pid_cwd[pid] = cwd 522 if self.debug > 1: 523 print("cwd=", cwd, file=self.debug_out) 524 continue 525 526 if w[0] == 'X': 527 try: 528 del self.pids[pid] 529 except KeyError: 530 pass 531 continue 532 533 if w[2] in self.seen: 534 if self.debug > 2: 535 print("seen:", w[2], file=self.debug_out) 536 continue 537 # file operations 538 if w[0] in 'ML': 539 # these are special, tread src as read and 540 # target as write 541 self.parse_path(w[2].strip("'"), cwd, 'R', w) 542 self.parse_path(w[3].strip("'"), cwd, 'W', w) 543 continue 544 elif w[0] in 'ERWS': 545 path = w[2] 546 if w[0] == 'E': 547 self.pids[pid] = path 548 elif path == '.': 549 continue 550 self.parse_path(path, cwd, w[0], w) 551 552 if version == 0: 553 raise AssertionError('missing filemon data') 554 if not eof_token: 555 raise AssertionError('truncated filemon data') 556 557 setid_pids = [] 558 # self.pids should be empty! 559 for pid,path in self.pids.items(): 560 try: 561 # no guarantee that path is still valid 562 if os.stat(path).st_mode & (stat.S_ISUID|stat.S_ISGID): 563 # we do not expect anything after Exec 564 setid_pids.append(pid) 565 continue 566 except: 567 # we do not care why the above fails, 568 # we do not want to miss the ERROR below. 569 pass 570 print("ERROR: missing eXit for {} pid {}".format(path, pid)) 571 for pid in setid_pids: 572 del self.pids[pid] 573 assert(len(self.pids) == 0) 574 if not file: 575 f.close() 576 577 def is_src(self, base, dir, rdir): 578 """is base in srctop""" 579 for dir in [dir,rdir]: 580 if not dir: 581 continue 582 path = '/'.join([dir,base]) 583 srctop = self.find_top(path, self.srctops) 584 if srctop: 585 if self.dpdeps: 586 self.add(self.file_deps, path.replace(srctop,''), 'file') 587 self.add(self.src_deps, dir.replace(srctop,''), 'src') 588 self.seenit(dir) 589 return True 590 return False 591 592 def parse_path(self, path, cwd, op=None, w=[]): 593 """look at a path for the op specified""" 594 595 if not op: 596 op = w[0] 597 598 # we are never interested in .dirdep files as dependencies 599 if path.endswith('.dirdep'): 600 return 601 for p in self.excludes: 602 if p and path.startswith(p): 603 if self.debug > 2: 604 print("exclude:", p, path, file=self.debug_out) 605 return 606 # we don't want to resolve the last component if it is 607 # a symlink 608 path = resolve(path, cwd, self.last_dir, self.debug, self.debug_out) 609 if not path: 610 return 611 dir,base = os.path.split(path) 612 if dir in self.seen: 613 if self.debug > 2: 614 print("seen:", dir, file=self.debug_out) 615 return 616 # we can have a path in an objdir which is a link 617 # to the src dir, we may need to add dependencies for each 618 rdir = dir 619 dir = abspath(dir, cwd, self.last_dir, self.debug, self.debug_out) 620 if dir: 621 rdir = os.path.realpath(dir) 622 else: 623 dir = rdir 624 if rdir == dir: 625 rdir = None 626 # now put path back together 627 path = '/'.join([dir,base]) 628 if self.debug > 1: 629 print("raw=%s rdir=%s dir=%s path=%s" % (w[2], rdir, dir, path), file=self.debug_out) 630 if op in 'RWS': 631 if path in [self.last_dir, cwd, self.cwd, self.curdir]: 632 if self.debug > 1: 633 print("skipping:", path, file=self.debug_out) 634 return 635 if os.path.isdir(path): 636 if op in 'RW': 637 self.last_dir = path; 638 if self.debug > 1: 639 print("ldir=", self.last_dir, file=self.debug_out) 640 return 641 642 if op in 'ER': 643 # finally, we get down to it 644 if dir == self.cwd or dir == self.curdir: 645 return 646 if self.is_src(base, dir, rdir): 647 self.seenit(w[2]) 648 if not rdir: 649 return 650 651 objroot = None 652 for dir in [dir,rdir]: 653 if not dir: 654 continue 655 objroot = self.find_top(dir, self.objroots) 656 if objroot: 657 break 658 if objroot: 659 ddep = self.find_obj(objroot, dir, path, w[2]) 660 if ddep: 661 self.add(self.obj_deps, ddep, 'obj') 662 if self.dpdeps and objroot.endswith('/stage/'): 663 sp = '/'.join(path.replace(objroot,'').split('/')[1:]) 664 self.add(self.file_deps, sp, 'file') 665 else: 666 # don't waste time looking again 667 self.seenit(w[2]) 668 self.seenit(dir) 669 670 671def main(argv, klass=MetaFile, xopts='', xoptf=None): 672 """Simple driver for class MetaFile. 673 674 Usage: 675 script [options] [key=value ...] "meta" ... 676 677 Options and key=value pairs contribute to the 678 dictionary passed to MetaFile. 679 680 -S "SRCTOP" 681 add "SRCTOP" to the "SRCTOPS" list. 682 683 -C "CURDIR" 684 685 -O "OBJROOT" 686 add "OBJROOT" to the "OBJROOTS" list. 687 688 -m "MACHINE" 689 690 -a "MACHINE_ARCH" 691 692 -H "HOST_TARGET" 693 694 -D "DPDEPS" 695 696 -d bumps debug level 697 698 """ 699 import getopt 700 701 # import Psyco if we can 702 # it can speed things up quite a bit 703 have_psyco = 0 704 try: 705 import psyco 706 psyco.full() 707 have_psyco = 1 708 except: 709 pass 710 711 conf = { 712 'SRCTOPS': [], 713 'OBJROOTS': [], 714 'EXCLUDES': [], 715 } 716 717 try: 718 machine = os.environ['MACHINE'] 719 if machine: 720 conf['MACHINE'] = machine 721 machine_arch = os.environ['MACHINE_ARCH'] 722 if machine_arch: 723 conf['MACHINE_ARCH'] = machine_arch 724 srctop = os.environ['SB_SRC'] 725 if srctop: 726 conf['SRCTOPS'].append(srctop) 727 objroot = os.environ['SB_OBJROOT'] 728 if objroot: 729 conf['OBJROOTS'].append(objroot) 730 except: 731 pass 732 733 debug = 0 734 output = True 735 736 opts, args = getopt.getopt(argv[1:], 'a:dS:C:O:R:m:D:H:qT:X:' + xopts) 737 for o, a in opts: 738 if o == '-a': 739 conf['MACHINE_ARCH'] = a 740 elif o == '-d': 741 debug += 1 742 elif o == '-q': 743 output = False 744 elif o == '-H': 745 conf['HOST_TARGET'] = a 746 elif o == '-S': 747 if a not in conf['SRCTOPS']: 748 conf['SRCTOPS'].append(a) 749 elif o == '-C': 750 conf['CURDIR'] = a 751 elif o == '-O': 752 if a not in conf['OBJROOTS']: 753 conf['OBJROOTS'].append(a) 754 elif o == '-R': 755 conf['RELDIR'] = a 756 elif o == '-D': 757 conf['DPDEPS'] = a 758 elif o == '-m': 759 conf['MACHINE'] = a 760 elif o == '-T': 761 conf['TARGET_SPEC'] = a 762 elif o == '-X': 763 if a not in conf['EXCLUDES']: 764 conf['EXCLUDES'].append(a) 765 elif xoptf: 766 xoptf(o, a, conf) 767 768 conf['debug'] = debug 769 770 # get any var=val assignments 771 eaten = [] 772 for a in args: 773 if a.find('=') > 0: 774 k,v = a.split('=') 775 if k in ['SRCTOP','OBJROOT','SRCTOPS','OBJROOTS']: 776 if k == 'SRCTOP': 777 k = 'SRCTOPS' 778 elif k == 'OBJROOT': 779 k = 'OBJROOTS' 780 if v not in conf[k]: 781 conf[k].append(v) 782 else: 783 conf[k] = v 784 eaten.append(a) 785 continue 786 break 787 788 for a in eaten: 789 args.remove(a) 790 791 debug_out = conf.get('debug_out', sys.stderr) 792 793 if debug: 794 print("config:", file=debug_out) 795 print("psyco=", have_psyco, file=debug_out) 796 for k,v in list(conf.items()): 797 print("%s=%s" % (k,v), file=debug_out) 798 799 m = None 800 for a in args: 801 if a.endswith('.meta'): 802 if not os.path.exists(a): 803 continue 804 m = klass(a, conf) 805 elif a.startswith('@'): 806 # there can actually multiple files per line 807 for line in open(a[1:]): 808 for f in line.strip().split(): 809 if not os.path.exists(f): 810 continue 811 m = klass(f, conf) 812 813 if output and m: 814 print(m.dirdeps()) 815 816 print(m.src_dirdeps('\nsrc:')) 817 818 dpdeps = conf.get('DPDEPS') 819 if dpdeps: 820 m.file_depends(open(dpdeps, 'w')) 821 822 return m 823 824if __name__ == '__main__': 825 try: 826 main(sys.argv) 827 except: 828 # yes, this goes to stdout 829 print("ERROR: ", sys.exc_info()[1]) 830 raise 831 832