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.51 2025/05/16 20:03:43 sjg Exp $ 43 44 Copyright (c) 2011-2025, 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 self.sb = conf.get('SB', '') 298 # we want the longest match 299 self.srctops.sort(reverse=True) 300 self.objroots.sort(reverse=True) 301 302 self.excludes = conf.get('EXCLUDES', []) 303 304 if self.debug: 305 print("host_target=", self.host_target, file=self.debug_out) 306 print("srctops=", self.srctops, file=self.debug_out) 307 print("objroots=", self.objroots, file=self.debug_out) 308 print("excludes=", self.excludes, file=self.debug_out) 309 print("ext_list=", self.exts, file=self.debug_out) 310 311 self.dirdep_re = re.compile(r'([^/]+)/(.+)') 312 313 if self.dpdeps and not self.reldir: 314 if self.debug: 315 print("need reldir:", end=' ', file=self.debug_out) 316 if self.curdir: 317 srctop = self.find_top(self.curdir, self.srctops) 318 if srctop: 319 self.reldir = self.curdir.replace(srctop,'') 320 if self.debug: 321 print(self.reldir, file=self.debug_out) 322 if not self.reldir: 323 self.dpdeps = None # we cannot do it? 324 325 self.cwd = os.getcwd() # make sure this is initialized 326 self.last_dir = self.cwd 327 328 if name: 329 self.try_parse() 330 331 def reset(self): 332 """reset state if we are being passed meta files from multiple directories.""" 333 self.seen = {} 334 self.obj_deps = [] 335 self.src_deps = [] 336 self.file_deps = [] 337 338 def dirdeps(self, sep='\n'): 339 """return DIRDEPS""" 340 return sep.strip() + sep.join(self.obj_deps) 341 342 def src_dirdeps(self, sep='\n'): 343 """return SRC_DIRDEPS""" 344 return sep.strip() + sep.join(self.src_deps) 345 346 def file_depends(self, out=None): 347 """Append DPDEPS_${file} += ${RELDIR} 348 for each file we saw, to the output file.""" 349 if not self.reldir: 350 return None 351 for f in sort_unique(self.file_deps): 352 print('DPDEPS_%s += %s' % (f, self.reldir), file=out) 353 # these entries provide for reverse DIRDEPS lookup 354 for f in self.obj_deps: 355 print('DEPDIRS_%s += %s' % (f, self.reldir), file=out) 356 357 def seenit(self, dir): 358 """rememer that we have seen dir.""" 359 self.seen[dir] = 1 360 361 def add(self, list, data, clue=''): 362 """add data to list if it isn't already there.""" 363 if data not in list: 364 list.append(data) 365 if self.debug: 366 print("%s: %sAdd: %s" % (self.name, clue, data), file=self.debug_out) 367 368 def find_top(self, path, list): 369 """the logical tree may be split across multiple trees""" 370 for top in list: 371 if path.startswith(top): 372 if self.debug > 2: 373 print("found in", top, file=self.debug_out) 374 return top 375 return None 376 377 def find_obj(self, objroot, dir, path, input): 378 """return path within objroot, taking care of .dirdep files""" 379 ddep = None 380 for ddepf in [path + '.dirdep', dir + '/.dirdep']: 381 if not ddep and os.path.exists(ddepf): 382 ddep = open(ddepf, 'r').readline().strip('# \n') 383 if self.debug > 1: 384 print("found %s: %s\n" % (ddepf, ddep), file=self.debug_out) 385 for e in self.exts: 386 if ddep.endswith(e): 387 ddep = ddep[0:-len(e)] 388 break 389 390 if not ddep: 391 # no .dirdeps, so remember that we've seen the raw input 392 self.seenit(input) 393 self.seenit(dir) 394 if self.machine == 'none': 395 if dir.startswith(objroot): 396 return dir.replace(objroot,'') 397 return None 398 m = self.dirdep_re.match(dir.replace(objroot,'')) 399 if m: 400 ddep = m.group(2) 401 dmachine = m.group(1) 402 if dmachine != self.machine: 403 if not (self.machine == 'host' and 404 dmachine == self.host_target): 405 if self.debug > 2: 406 print("adding .%s to %s" % (dmachine, ddep), file=self.debug_out) 407 ddep += '.' + dmachine 408 409 return ddep 410 411 def try_parse(self, name=None, file=None): 412 """give file and line number causing exception""" 413 try: 414 self.parse(name, file) 415 except: 416 # give a useful clue 417 print('{}:{}: '.format(self.name, self.line), end=' ', file=sys.stderr) 418 raise 419 420 def parse(self, name=None, file=None): 421 """A meta file looks like: 422 423 # Meta data file "path" 424 CMD "command-line" 425 CWD "cwd" 426 TARGET "target" 427 -- command output -- 428 -- filemon acquired metadata -- 429 # buildmon version 3 430 V 3 431 C "pid" "cwd" 432 E "pid" "path" 433 F "pid" "child" 434 R "pid" "path" 435 W "pid" "path" 436 X "pid" "status" 437 D "pid" "path" 438 L "pid" "src" "target" 439 M "pid" "old" "new" 440 S "pid" "path" 441 # Bye bye 442 443 We go to some effort to avoid processing a dependency more than once. 444 Of the above record types only C,E,F,L,R,V and W are of interest. 445 """ 446 447 version = 0 # unknown 448 if name: 449 self.name = name; 450 if file: 451 f = file 452 cwd = self.last_dir = self.cwd 453 else: 454 f = open(self.name, 'r') 455 skip = True 456 pid_cwd = {} 457 pid_last_dir = {} 458 last_pid = 0 459 eof_token = False 460 461 self.line = 0 462 if self.curdir: 463 self.seenit(self.curdir) # we ignore this 464 465 if self.sb and self.name.startswith(self.sb): 466 error_name = self.name.replace(self.sb+'/','') 467 else: 468 error_name = self.name 469 interesting = '#CEFLRVX' 470 for line in f: 471 self.line += 1 472 # ignore anything we don't care about 473 if not line[0] in interesting: 474 continue 475 if self.debug > 2: 476 print("input:", line, end=' ', file=self.debug_out) 477 w = line.split() 478 479 if skip: 480 if w[0] == 'V': 481 skip = False 482 version = int(w[1]) 483 """ 484 if version < 4: 485 # we cannot ignore 'W' records 486 # as they may be 'rw' 487 interesting += 'W' 488 """ 489 elif w[0] == 'CWD': 490 self.cwd = cwd = self.last_dir = w[1] 491 self.seenit(cwd) # ignore this 492 if self.debug: 493 print("%s: CWD=%s" % (self.name, cwd), file=self.debug_out) 494 continue 495 496 if w[0] == '#': 497 # check the file has not been truncated 498 if line.find('Bye') > 0: 499 eof_token = True 500 continue 501 502 pid = int(w[1]) 503 if pid != last_pid: 504 if last_pid: 505 pid_last_dir[last_pid] = self.last_dir 506 cwd = pid_cwd.get(pid, self.cwd) 507 self.last_dir = pid_last_dir.get(pid, self.cwd) 508 last_pid = pid 509 510 # process operations 511 if w[0] == 'F': 512 npid = int(w[2]) 513 pid_cwd[npid] = cwd 514 pid_last_dir[npid] = cwd 515 last_pid = npid 516 continue 517 elif w[0] == 'C': 518 cwd = abspath(w[2], cwd, None, self.debug, self.debug_out) 519 if not cwd: 520 cwd = w[2] 521 if self.debug > 1: 522 print("missing cwd=", cwd, file=self.debug_out) 523 if cwd.endswith('/.'): 524 cwd = cwd[0:-2] 525 self.last_dir = pid_last_dir[pid] = cwd 526 pid_cwd[pid] = cwd 527 if self.debug > 1: 528 print("cwd=", cwd, file=self.debug_out) 529 continue 530 531 if w[0] == 'X': 532 try: 533 del self.pids[pid] 534 except KeyError: 535 pass 536 continue 537 538 if w[2] in self.seen: 539 if self.debug > 2: 540 print("seen:", w[2], file=self.debug_out) 541 continue 542 # file operations 543 if w[0] in 'ML': 544 # these are special, tread src as read and 545 # target as write 546 self.parse_path(w[3].strip("'"), cwd, 'W', w) 547 self.parse_path(w[2].strip("'"), cwd, 'R', w) 548 continue 549 elif w[0] in 'ERWS': 550 path = w[2] 551 if w[0] == 'E': 552 self.pids[pid] = path 553 elif path == '.': 554 continue 555 self.parse_path(path, cwd, w[0], w) 556 557 if version == 0: 558 raise AssertionError('missing filemon data: {}'.format(error_name)) 559 if not eof_token: 560 raise AssertionError('truncated filemon data: {}'.format(error_name)) 561 562 setid_pids = [] 563 # self.pids should be empty! 564 for pid,path in self.pids.items(): 565 try: 566 # no guarantee that path is still valid 567 if os.stat(path).st_mode & (stat.S_ISUID|stat.S_ISGID): 568 # we do not expect anything after Exec 569 setid_pids.append(pid) 570 continue 571 except: 572 # we do not care why the above fails, 573 # we do not want to miss the ERROR below. 574 pass 575 print("ERROR: missing eXit for {} pid {}".format(path, pid)) 576 for pid in setid_pids: 577 del self.pids[pid] 578 if len(self.pids) > 0: 579 raise AssertionError('bad filemon data - missing eXits: {}'.format(error_name)) 580 if not file: 581 f.close() 582 583 def is_src(self, base, dir, rdir): 584 """is base in srctop""" 585 for dir in [dir,rdir]: 586 if not dir: 587 continue 588 path = '/'.join([dir,base]) 589 srctop = self.find_top(path, self.srctops) 590 if srctop: 591 if self.dpdeps: 592 self.add(self.file_deps, path.replace(srctop,''), 'file') 593 self.add(self.src_deps, dir.replace(srctop,''), 'src') 594 self.seenit(dir) 595 return True 596 return False 597 598 def parse_path(self, path, cwd, op=None, w=[]): 599 """look at a path for the op specified""" 600 601 if not op: 602 op = w[0] 603 604 # we are never interested in .dirdep files as dependencies 605 if path.endswith('.dirdep'): 606 return 607 for p in self.excludes: 608 if p and path.startswith(p): 609 if self.debug > 2: 610 print("exclude:", p, path, file=self.debug_out) 611 return 612 # we don't want to resolve the last component if it is 613 # a symlink 614 npath = resolve(path, cwd, self.last_dir, self.debug, self.debug_out) 615 if not npath: 616 if len(w) > 3 and w[0] in 'ML' and op == 'R' and path.startswith('../'): 617 # we already resolved the target of the M/L 618 # so it makes sense to try and resolve relative to that dir. 619 if os.path.isdir(self.last_path): 620 dir = self.last_path 621 else: 622 dir,junk = os.path.split(self.last_path) 623 npath = resolve(path, cwd, dir, self.debug, self.debug_out) 624 if not npath: 625 return 626 path = npath 627 dir,base = os.path.split(path) 628 if dir in self.seen: 629 if self.debug > 2: 630 print("seen:", dir, file=self.debug_out) 631 return 632 # we can have a path in an objdir which is a link 633 # to the src dir, we may need to add dependencies for each 634 rdir = dir 635 dir = abspath(dir, cwd, self.last_dir, self.debug, self.debug_out) 636 if dir: 637 rdir = os.path.realpath(dir) 638 else: 639 dir = rdir 640 if rdir == dir: 641 rdir = None 642 # now put path back together 643 path = '/'.join([dir,base]) 644 self.last_path = path 645 if self.debug > 1: 646 print("raw=%s rdir=%s dir=%s path=%s" % (w[2], rdir, dir, path), file=self.debug_out) 647 if op in 'RWS': 648 if path in [self.last_dir, cwd, self.cwd, self.curdir]: 649 if self.debug > 1: 650 print("skipping:", path, file=self.debug_out) 651 return 652 if os.path.isdir(path): 653 if op in 'RW': 654 self.last_dir = path; 655 if self.debug > 1: 656 print("ldir=", self.last_dir, file=self.debug_out) 657 return 658 659 if op in 'ER': 660 # finally, we get down to it 661 if dir == self.cwd or dir == self.curdir: 662 return 663 if self.is_src(base, dir, rdir): 664 self.seenit(w[2]) 665 if not rdir: 666 return 667 668 objroot = None 669 for dir in [dir,rdir]: 670 if not dir: 671 continue 672 objroot = self.find_top(dir, self.objroots) 673 if objroot: 674 break 675 if objroot: 676 ddep = self.find_obj(objroot, dir, path, w[2]) 677 if ddep: 678 self.add(self.obj_deps, ddep, 'obj') 679 if self.dpdeps and objroot.endswith('/stage/'): 680 sp = '/'.join(path.replace(objroot,'').split('/')[1:]) 681 self.add(self.file_deps, sp, 'file') 682 else: 683 # don't waste time looking again 684 self.seenit(w[2]) 685 self.seenit(dir) 686 687 688def main(argv, klass=MetaFile, xopts='', xoptf=None): 689 """Simple driver for class MetaFile. 690 691 Usage: 692 script [options] [key=value ...] "meta" ... 693 694 Options and key=value pairs contribute to the 695 dictionary passed to MetaFile. 696 697 -S "SRCTOP" 698 add "SRCTOP" to the "SRCTOPS" list. 699 700 -C "CURDIR" 701 702 -O "OBJROOT" 703 add "OBJROOT" to the "OBJROOTS" list. 704 705 -m "MACHINE" 706 707 -a "MACHINE_ARCH" 708 709 -H "HOST_TARGET" 710 711 -D "DPDEPS" 712 713 -d bumps debug level 714 715 """ 716 import getopt 717 718 # import Psyco if we can 719 # it can speed things up quite a bit 720 have_psyco = 0 721 try: 722 import psyco 723 psyco.full() 724 have_psyco = 1 725 except: 726 pass 727 728 conf = { 729 'SRCTOPS': [], 730 'OBJROOTS': [], 731 'EXCLUDES': [], 732 } 733 734 conf['SB'] = os.getenv('SB', '') 735 736 try: 737 machine = os.environ['MACHINE'] 738 if machine: 739 conf['MACHINE'] = machine 740 machine_arch = os.environ['MACHINE_ARCH'] 741 if machine_arch: 742 conf['MACHINE_ARCH'] = machine_arch 743 srctop = os.environ['SB_SRC'] 744 if srctop: 745 conf['SRCTOPS'].append(srctop) 746 objroot = os.environ['SB_OBJROOT'] 747 if objroot: 748 conf['OBJROOTS'].append(objroot) 749 except: 750 pass 751 752 debug = 0 753 output = True 754 755 opts, args = getopt.getopt(argv[1:], 'a:dS:C:O:R:m:D:H:qT:X:' + xopts) 756 for o, a in opts: 757 if o == '-a': 758 conf['MACHINE_ARCH'] = a 759 elif o == '-d': 760 debug += 1 761 elif o == '-q': 762 output = False 763 elif o == '-H': 764 conf['HOST_TARGET'] = a 765 elif o == '-S': 766 if a not in conf['SRCTOPS']: 767 conf['SRCTOPS'].append(a) 768 elif o == '-C': 769 conf['CURDIR'] = a 770 elif o == '-O': 771 if a not in conf['OBJROOTS']: 772 conf['OBJROOTS'].append(a) 773 elif o == '-R': 774 conf['RELDIR'] = a 775 elif o == '-D': 776 conf['DPDEPS'] = a 777 elif o == '-m': 778 conf['MACHINE'] = a 779 elif o == '-T': 780 conf['TARGET_SPEC'] = a 781 elif o == '-X': 782 if a not in conf['EXCLUDES']: 783 conf['EXCLUDES'].append(a) 784 elif xoptf: 785 xoptf(o, a, conf) 786 787 conf['debug'] = debug 788 789 # get any var=val assignments 790 eaten = [] 791 for a in args: 792 if a.find('=') > 0: 793 k,v = a.split('=') 794 if k in ['SRCTOP','OBJROOT','SRCTOPS','OBJROOTS']: 795 if k == 'SRCTOP': 796 k = 'SRCTOPS' 797 elif k == 'OBJROOT': 798 k = 'OBJROOTS' 799 if v not in conf[k]: 800 conf[k].append(v) 801 else: 802 conf[k] = v 803 eaten.append(a) 804 continue 805 break 806 807 for a in eaten: 808 args.remove(a) 809 810 debug_out = conf.get('debug_out', sys.stderr) 811 812 if debug: 813 print("config:", file=debug_out) 814 print("psyco=", have_psyco, file=debug_out) 815 for k,v in list(conf.items()): 816 print("%s=%s" % (k,v), file=debug_out) 817 818 m = None 819 for a in args: 820 if a.endswith('.meta'): 821 if not os.path.exists(a): 822 continue 823 m = klass(a, conf) 824 elif a.startswith('@'): 825 # there can actually multiple files per line 826 for line in open(a[1:]): 827 for f in line.strip().split(): 828 if not os.path.exists(f): 829 continue 830 m = klass(f, conf) 831 832 if output and m: 833 print(m.dirdeps()) 834 835 print(m.src_dirdeps('\nsrc:')) 836 837 dpdeps = conf.get('DPDEPS') 838 if dpdeps: 839 m.file_depends(open(dpdeps, 'w')) 840 841 return m 842 843if __name__ == '__main__': 844 try: 845 main(sys.argv) 846 except: 847 # yes, this goes to stdout 848 print("ERROR: ", sys.exc_info()[1]) 849 raise 850 851