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.54 2025/07/24 16:05:48 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,M,R,V,W and X 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 = '#CEFLMRVX' 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 wlen = len(w) 479 480 if skip: 481 if w[0] == 'V': 482 skip = False 483 version = int(w[1]) 484 """ 485 if version < 4: 486 # we cannot ignore 'W' records 487 # as they may be 'rw' 488 interesting += 'W' 489 """ 490 elif w[0] == 'CWD': 491 self.cwd = cwd = self.last_dir = w[1] 492 self.seenit(cwd) # ignore this 493 if self.debug: 494 print("%s: CWD=%s" % (self.name, cwd), file=self.debug_out) 495 continue 496 497 if w[0] == '#': 498 # check the file has not been truncated 499 if line.find('Bye') > 0: 500 eof_token = True 501 continue 502 else: 503 # before we go further check we have a sane number of args 504 # the Linux filemon module is rather unreliable. 505 if w[0] in 'LM': 506 elen = 4 507 elif w[0] == 'X': 508 # at least V4 on Linux does 3 args 509 if wlen == 3: 510 elen = 3 511 else: 512 elen = 4 513 else: 514 elen = 3 515 if self.debug > 2: 516 print('op={} elen={} wlen={} line="{}"'.format(w[0], elen, wlen, line.strip()), file=self.debug_out) 517 if wlen != elen: 518 raise AssertionError('corrupted filemon data: wrong number of words: expected {} got {} in: {}'.format(elen, wlen, line)) 519 520 pid = int(w[1]) 521 if pid != last_pid: 522 if last_pid: 523 pid_last_dir[last_pid] = self.last_dir 524 cwd = pid_cwd.get(pid, self.cwd) 525 self.last_dir = pid_last_dir.get(pid, self.cwd) 526 last_pid = pid 527 528 # process operations 529 if w[0] == 'F': 530 npid = int(w[2]) 531 pid_cwd[npid] = cwd 532 pid_last_dir[npid] = cwd 533 last_pid = npid 534 continue 535 elif w[0] == 'C': 536 cwd = abspath(w[2], cwd, None, self.debug, self.debug_out) 537 if not cwd: 538 cwd = w[2] 539 if self.debug > 1: 540 print("missing cwd=", cwd, file=self.debug_out) 541 if cwd.endswith('/.'): 542 cwd = cwd[0:-2] 543 self.last_dir = pid_last_dir[pid] = cwd 544 pid_cwd[pid] = cwd 545 if self.debug > 1: 546 print("cwd=", cwd, file=self.debug_out) 547 continue 548 549 if w[0] == 'X': 550 try: 551 del self.pids[pid] 552 except KeyError: 553 pass 554 continue 555 556 if w[2] in self.seen: 557 if self.debug > 2: 558 print("seen:", w[2], file=self.debug_out) 559 continue 560 # file operations 561 if w[0] in 'LM': 562 # these are special, tread src as read and 563 # target as write 564 self.parse_path(w[3].strip("'"), cwd, 'W', w) 565 self.parse_path(w[2].strip("'"), cwd, 'R', w) 566 continue 567 elif w[0] in 'ERWS': 568 path = w[2] 569 if w[0] == 'E': 570 self.pids[pid] = path 571 elif path == '.': 572 continue 573 self.parse_path(path, cwd, w[0], w) 574 575 if version == 0: 576 raise AssertionError('missing filemon data: {}'.format(error_name)) 577 if not eof_token: 578 raise AssertionError('truncated filemon data: {}'.format(error_name)) 579 580 setid_pids = [] 581 # self.pids should be empty! 582 for pid,path in self.pids.items(): 583 try: 584 # no guarantee that path is still valid 585 if os.stat(path).st_mode & (stat.S_ISUID|stat.S_ISGID): 586 # we do not expect anything after Exec 587 setid_pids.append(pid) 588 continue 589 except: 590 # we do not care why the above fails, 591 # we do not want to miss the ERROR below. 592 pass 593 print("ERROR: missing eXit for {} pid {}".format(path, pid)) 594 for pid in setid_pids: 595 del self.pids[pid] 596 if len(self.pids) > 0: 597 raise AssertionError('bad filemon data - missing eXits: {}'.format(error_name)) 598 if not file: 599 f.close() 600 601 def is_src(self, base, dir, rdir): 602 """is base in srctop""" 603 for dir in [dir,rdir]: 604 if not dir: 605 continue 606 path = '/'.join([dir,base]) 607 srctop = self.find_top(path, self.srctops) 608 if srctop: 609 if self.dpdeps: 610 self.add(self.file_deps, path.replace(srctop,''), 'file') 611 self.add(self.src_deps, dir.replace(srctop,''), 'src') 612 self.seenit(dir) 613 return True 614 return False 615 616 def parse_path(self, path, cwd, op=None, w=[]): 617 """look at a path for the op specified""" 618 619 if not op: 620 op = w[0] 621 622 # we are never interested in .dirdep files as dependencies 623 if path.endswith('.dirdep'): 624 return 625 for p in self.excludes: 626 if p and path.startswith(p): 627 if self.debug > 2: 628 print("exclude:", p, path, file=self.debug_out) 629 return 630 # we don't want to resolve the last component if it is 631 # a symlink 632 npath = resolve(path, cwd, self.last_dir, self.debug, self.debug_out) 633 if not npath: 634 if len(w) > 3 and w[0] in 'ML' and op == 'R' and path.startswith('../'): 635 # we already resolved the target of the M/L 636 # so it makes sense to try and resolve relative to that dir. 637 if os.path.isdir(self.last_path): 638 dir = self.last_path 639 else: 640 dir,junk = os.path.split(self.last_path) 641 npath = resolve(path, cwd, dir, self.debug, self.debug_out) 642 if not npath: 643 return 644 path = npath 645 dir,base = os.path.split(path) 646 if dir in self.seen: 647 if self.debug > 2: 648 print("seen:", dir, file=self.debug_out) 649 return 650 # we can have a path in an objdir which is a link 651 # to the src dir, we may need to add dependencies for each 652 rdir = dir 653 dir = abspath(dir, cwd, self.last_dir, self.debug, self.debug_out) 654 if dir: 655 rdir = os.path.realpath(dir) 656 else: 657 dir = rdir 658 if rdir == dir: 659 rdir = None 660 # now put path back together 661 path = '/'.join([dir,base]) 662 self.last_path = path 663 if self.debug > 1: 664 print("raw=%s rdir=%s dir=%s path=%s" % (w[2], rdir, dir, path), file=self.debug_out) 665 if op in 'RWS': 666 if path in [self.last_dir, cwd, self.cwd, self.curdir]: 667 if self.debug > 1: 668 print("skipping:", path, file=self.debug_out) 669 return 670 if os.path.isdir(path): 671 if op in 'RW': 672 self.last_dir = path; 673 if self.debug > 1: 674 print("ldir=", self.last_dir, file=self.debug_out) 675 return 676 677 if op in 'ER': 678 # finally, we get down to it 679 if dir == self.cwd or dir == self.curdir: 680 return 681 if self.is_src(base, dir, rdir): 682 self.seenit(w[2]) 683 if not rdir: 684 return 685 686 objroot = None 687 for dir in [dir,rdir]: 688 if not dir: 689 continue 690 objroot = self.find_top(dir, self.objroots) 691 if objroot: 692 break 693 if objroot: 694 ddep = self.find_obj(objroot, dir, path, w[2]) 695 if ddep: 696 self.add(self.obj_deps, ddep, 'obj') 697 if self.dpdeps and objroot.endswith('/stage/'): 698 sp = '/'.join(path.replace(objroot,'').split('/')[1:]) 699 self.add(self.file_deps, sp, 'file') 700 else: 701 # don't waste time looking again 702 self.seenit(w[2]) 703 self.seenit(dir) 704 705 706def main(argv, klass=MetaFile, xopts='', xoptf=None): 707 """Simple driver for class MetaFile. 708 709 Usage: 710 script [options] [key=value ...] "meta" ... 711 712 Options and key=value pairs contribute to the 713 dictionary passed to MetaFile. 714 715 -S "SRCTOP" 716 add "SRCTOP" to the "SRCTOPS" list. 717 718 -C "CURDIR" 719 720 -O "OBJROOT" 721 add "OBJROOT" to the "OBJROOTS" list. 722 723 -m "MACHINE" 724 725 -a "MACHINE_ARCH" 726 727 -H "HOST_TARGET" 728 729 -D "DPDEPS" 730 731 -d bumps debug level 732 733 """ 734 import getopt 735 736 # import Psyco if we can 737 # it can speed things up quite a bit 738 have_psyco = 0 739 try: 740 import psyco 741 psyco.full() 742 have_psyco = 1 743 except: 744 pass 745 746 conf = { 747 'SRCTOPS': [], 748 'OBJROOTS': [], 749 'EXCLUDES': [], 750 } 751 752 conf['SB'] = os.getenv('SB', '') 753 754 try: 755 machine = os.environ['MACHINE'] 756 if machine: 757 conf['MACHINE'] = machine 758 machine_arch = os.environ['MACHINE_ARCH'] 759 if machine_arch: 760 conf['MACHINE_ARCH'] = machine_arch 761 srctop = os.environ['SB_SRC'] 762 if srctop: 763 conf['SRCTOPS'].append(srctop) 764 objroot = os.environ['SB_OBJROOT'] 765 if objroot: 766 conf['OBJROOTS'].append(objroot) 767 except: 768 pass 769 770 debug = 0 771 output = True 772 773 opts, args = getopt.getopt(argv[1:], 'a:dS:C:O:R:m:D:H:qT:X:' + xopts) 774 for o, a in opts: 775 if o == '-a': 776 conf['MACHINE_ARCH'] = a 777 elif o == '-d': 778 debug += 1 779 elif o == '-q': 780 output = False 781 elif o == '-H': 782 conf['HOST_TARGET'] = a 783 elif o == '-S': 784 if a not in conf['SRCTOPS']: 785 conf['SRCTOPS'].append(a) 786 elif o == '-C': 787 conf['CURDIR'] = a 788 elif o == '-O': 789 if a not in conf['OBJROOTS']: 790 conf['OBJROOTS'].append(a) 791 elif o == '-R': 792 conf['RELDIR'] = a 793 elif o == '-D': 794 conf['DPDEPS'] = a 795 elif o == '-m': 796 conf['MACHINE'] = a 797 elif o == '-T': 798 conf['TARGET_SPEC'] = a 799 elif o == '-X': 800 if a not in conf['EXCLUDES']: 801 conf['EXCLUDES'].append(a) 802 elif xoptf: 803 xoptf(o, a, conf) 804 805 conf['debug'] = debug 806 807 # get any var=val assignments 808 eaten = [] 809 for a in args: 810 if a.find('=') > 0: 811 k,v = a.split('=') 812 if k in ['SRCTOP','OBJROOT','SRCTOPS','OBJROOTS']: 813 if k == 'SRCTOP': 814 k = 'SRCTOPS' 815 elif k == 'OBJROOT': 816 k = 'OBJROOTS' 817 if v not in conf[k]: 818 conf[k].append(v) 819 else: 820 conf[k] = v 821 eaten.append(a) 822 continue 823 break 824 825 for a in eaten: 826 args.remove(a) 827 828 debug_out = conf.get('debug_out', sys.stderr) 829 830 if debug: 831 print("config:", file=debug_out) 832 print("psyco=", have_psyco, file=debug_out) 833 for k,v in list(conf.items()): 834 print("%s=%s" % (k,v), file=debug_out) 835 836 m = None 837 for a in args: 838 if a.endswith('.meta'): 839 if not os.path.exists(a): 840 continue 841 m = klass(a, conf) 842 elif a.startswith('@'): 843 # there can actually multiple files per line 844 for line in open(a[1:]): 845 for f in line.strip().split(): 846 if not os.path.exists(f): 847 continue 848 m = klass(f, conf) 849 850 if output and m: 851 print(m.dirdeps()) 852 853 print(m.src_dirdeps('\nsrc:')) 854 855 dpdeps = conf.get('DPDEPS') 856 if dpdeps: 857 m.file_depends(open(dpdeps, 'w')) 858 859 return m 860 861if __name__ == '__main__': 862 try: 863 main(sys.argv) 864 except: 865 # yes, this goes to stdout 866 print("ERROR: ", sys.exc_info()[1]) 867 raise 868 869