1#!/usr/bin/env python 2 3from __future__ import print_function 4 5""" 6This script parses each "meta" file and extracts the 7information needed to deduce build and src dependencies. 8 9It works much the same as the original shell script, but is 10*much* more efficient. 11 12The parsing work is handled by the class MetaFile. 13We only pay attention to a subset of the information in the 14"meta" files. Specifically: 15 16'CWD' to initialize our notion. 17 18'C' to track chdir(2) on a per process basis 19 20'R' files read are what we really care about. 21 directories read, provide a clue to resolving 22 subsequent relative paths. That is if we cannot find 23 them relative to 'cwd', we check relative to the last 24 dir read. 25 26'W' files opened for write or read-write, 27 for filemon V3 and earlier. 28 29'E' files executed. 30 31'L' files linked 32 33'V' the filemon version, this record is used as a clue 34 that we have reached the interesting bit. 35 36""" 37 38""" 39SPDX-License-Identifier: BSD-2-Clause 40 41RCSid: 42 $Id: meta2deps.py,v 1.47 2024/02/17 17:26:57 sjg Exp $ 43 44 Copyright (c) 2011-2020, Simon J. Gerraty 45 Copyright (c) 2011-2017, Juniper Networks, Inc. 46 All rights reserved. 47 48 Redistribution and use in source and binary forms, with or without 49 modification, are permitted provided that the following conditions 50 are met: 51 1. Redistributions of source code must retain the above copyright 52 notice, this list of conditions and the following disclaimer. 53 2. Redistributions in binary form must reproduce the above copyright 54 notice, this list of conditions and the following disclaimer in the 55 documentation and/or other materials provided with the distribution. 56 57 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 58 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 59 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 60 A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 61 OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 62 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 63 LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 64 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 65 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 66 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 67 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 68 69""" 70 71import os 72import re 73import sys 74import stat 75 76def resolve(path, cwd, last_dir=None, debug=0, debug_out=sys.stderr): 77 """ 78 Return an absolute path, resolving via cwd or last_dir if needed. 79 80 Cleanup any leading ``./`` and trailing ``/.`` 81 """ 82 while path.endswith('/.'): 83 path = path[0:-2] 84 if len(path) > 0 and path[0] == '/': 85 if os.path.exists(path): 86 return path 87 if debug > 2: 88 print("skipping non-existent:", path, file=debug_out) 89 return None 90 if path == '.': 91 return cwd 92 if path.startswith('./'): 93 while path.startswith('./'): 94 path = path[1:] 95 return cwd + path 96 if last_dir == cwd: 97 last_dir = None 98 for d in [last_dir, cwd]: 99 if not d: 100 continue 101 if path == '..': 102 dw = d.split('/') 103 p = '/'.join(dw[:-1]) 104 if not p: 105 p = '/' 106 return p 107 p = '/'.join([d,path]) 108 if debug > 2: 109 print("looking for:", p, end=' ', file=debug_out) 110 if not os.path.exists(p): 111 if debug > 2: 112 print("nope", file=debug_out) 113 p = None 114 continue 115 if debug > 2: 116 print("found:", p, file=debug_out) 117 return p 118 return None 119 120def cleanpath(path): 121 """cleanup path without using realpath(3)""" 122 if path.startswith('/'): 123 r = '/' 124 else: 125 r = '' 126 p = [] 127 w = path.split('/') 128 for d in w: 129 if not d or d == '.': 130 continue 131 if d == '..': 132 try: 133 p.pop() 134 continue 135 except: 136 break 137 p.append(d) 138 139 return r + '/'.join(p) 140 141def abspath(path, cwd, last_dir=None, debug=0, debug_out=sys.stderr): 142 """ 143 Return an absolute path, resolving via cwd or last_dir if needed. 144 this gets called a lot, so we try to avoid calling realpath. 145 """ 146 rpath = resolve(path, cwd, last_dir, debug, debug_out) 147 if rpath: 148 path = rpath 149 elif len(path) > 0 and path[0] == '/': 150 return None 151 if (path.find('/') < 0 or 152 path.find('./') > 0 or 153 path.find('/../') > 0 or 154 path.endswith('/..')): 155 path = cleanpath(path) 156 return path 157 158def sort_unique(list, cmp=None, key=None, reverse=False): 159 if sys.version_info[0] == 2: 160 list.sort(cmp, key, reverse) 161 else: 162 list.sort(reverse=reverse) 163 nl = [] 164 le = None 165 for e in list: 166 if e == le: 167 continue 168 le = e 169 nl.append(e) 170 return nl 171 172def add_trims(x): 173 return ['/' + x + '/', 174 '/' + x, 175 x + '/', 176 x] 177 178def target_spec_exts(target_spec): 179 """return a list of dirdep extensions that could match target_spec""" 180 181 if target_spec.find(',') < 0: 182 return ['.'+target_spec] 183 w = target_spec.split(',') 184 n = len(w) 185 e = [] 186 while n > 0: 187 e.append('.'+','.join(w[0:n])) 188 n -= 1 189 return e 190 191class MetaFile: 192 """class to parse meta files generated by bmake.""" 193 194 conf = None 195 dirdep_re = None 196 host_target = None 197 srctops = [] 198 objroots = [] 199 excludes = [] 200 seen = {} 201 obj_deps = [] 202 src_deps = [] 203 file_deps = [] 204 205 def __init__(self, name, conf={}): 206 """if name is set we will parse it now. 207 conf can have the follwing keys: 208 209 SRCTOPS list of tops of the src tree(s). 210 211 CURDIR the src directory 'bmake' was run from. 212 213 RELDIR the relative path from SRCTOP to CURDIR 214 215 MACHINE the machine we built for. 216 set to 'none' if we are not cross-building. 217 More specifically if machine cannot be deduced from objdirs. 218 219 TARGET_SPEC 220 Sometimes MACHINE isn't enough. 221 222 HOST_TARGET 223 when we build for the pseudo machine 'host' 224 the object tree uses HOST_TARGET rather than MACHINE. 225 226 OBJROOTS a list of the common prefix for all obj dirs it might 227 end in '/' or '-'. 228 229 DPDEPS names an optional file to which per file dependencies 230 will be appended. 231 For example if 'some/path/foo.h' is read from SRCTOP 232 then 'DPDEPS_some/path/foo.h +=' "RELDIR" is output. 233 This can allow 'bmake' to learn all the dirs within 234 the tree that depend on 'foo.h' 235 236 EXCLUDES 237 A list of paths to ignore. 238 ccache(1) can otherwise be trouble. 239 240 debug desired debug level 241 242 debug_out open file to send debug output to (sys.stderr) 243 244 """ 245 246 self.name = name 247 self.debug = conf.get('debug', 0) 248 self.debug_out = conf.get('debug_out', sys.stderr) 249 250 self.machine = conf.get('MACHINE', '') 251 self.machine_arch = conf.get('MACHINE_ARCH', '') 252 self.target_spec = conf.get('TARGET_SPEC', self.machine) 253 self.exts = target_spec_exts(self.target_spec) 254 self.curdir = conf.get('CURDIR') 255 self.reldir = conf.get('RELDIR') 256 self.dpdeps = conf.get('DPDEPS') 257 self.pids = {} 258 self.line = 0 259 260 if not self.conf: 261 # some of the steps below we want to do only once 262 self.conf = conf 263 self.host_target = conf.get('HOST_TARGET') 264 for srctop in conf.get('SRCTOPS', []): 265 if srctop[-1] != '/': 266 srctop += '/' 267 if not srctop in self.srctops: 268 self.srctops.append(srctop) 269 _srctop = os.path.realpath(srctop) 270 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