comparison mxtool/mx.py @ 11605:3676540f71cf

Allow mxtool suites to be in separate repositories (CR-1367)
author Mick Jordan <mick.jordan@oracle.com>
date Wed, 11 Sep 2013 21:04:24 +0200
parents 723796685546
children 60e3fe0fe939
comparison
equal deleted inserted replaced
11604:943f1863e1c1 11605:3676540f71cf
35 35
36 The organizing principle of mx is a project suite. A suite is a directory 36 The organizing principle of mx is a project suite. A suite is a directory
37 containing one or more projects. It's not coincidental that this closely 37 containing one or more projects. It's not coincidental that this closely
38 matches the layout of one or more projects in a Mercurial repository. 38 matches the layout of one or more projects in a Mercurial repository.
39 The configuration information for a suite lives in an 'mx' sub-directory 39 The configuration information for a suite lives in an 'mx' sub-directory
40 at the top level of the suite. 40 at the top level of the suite. A suite is given a name by a 'suite=name'
41 property in the 'mx/projects' file (if omitted the name is suite directory).
42 An 'mx' subdirectory can be named as plain 'mx' or 'mxbasename', where
43 'basename' is the os.path.basename of the suite directory.
44 The latter is useful to avoid clashes in IDE project names.
41 45
42 When launched, mx treats the current working directory as a suite. 46 When launched, mx treats the current working directory as a suite.
43 This is the primary suite. All other suites are called included suites. 47 This is the primary suite. All other suites are called included suites.
44 48
45 The configuration files (i.e. in the 'mx' sub-directory) of a suite are: 49 The configuration files (i.e. in the 'mx' sub-directory) of a suite are:
135 by extension commands. 139 by extension commands.
136 140
137 Property values can use environment variables with Bash syntax (e.g. ${HOME}). 141 Property values can use environment variables with Bash syntax (e.g. ${HOME}).
138 """ 142 """
139 143
140 import sys, os, errno, time, subprocess, shlex, types, urllib2, contextlib, StringIO, zipfile, signal, xml.sax.saxutils, tempfile 144 import sys, os, errno, time, subprocess, shlex, types, urllib2, contextlib, StringIO, zipfile, signal, xml.sax.saxutils, tempfile, fnmatch
141 import textwrap 145 import textwrap
142 import xml.parsers.expat 146 import xml.parsers.expat
143 import shutil, re, xml.dom.minidom 147 import shutil, re, xml.dom.minidom
144 from collections import Callable 148 from collections import Callable
145 from threading import Thread 149 from threading import Thread
433 self.sourceUrls = sourceUrls 437 self.sourceUrls = sourceUrls
434 for url in urls: 438 for url in urls:
435 if url.endswith('/') != self.path.endswith(os.sep): 439 if url.endswith('/') != self.path.endswith(os.sep):
436 abort('Path for dependency directory must have a URL ending with "/": path=' + self.path + ' url=' + url) 440 abort('Path for dependency directory must have a URL ending with "/": path=' + self.path + ' url=' + url)
437 441
442 def __eq__(self, other):
443 if isinstance(other, Library):
444 if len(self.urls) == 0:
445 return self.path == other.path
446 else:
447 return self.urls == other.urls
448 else:
449 return NotImplemented
450
451
452 def __ne__(self, other):
453 result = self.__eq__(other)
454 if result is NotImplemented:
455 return result
456 return not result
457
458
438 def get_path(self, resolve): 459 def get_path(self, resolve):
439 path = self.path 460 path = self.path
440 if not isabs(path): 461 if not isabs(path):
441 path = join(self.suite.dir, path) 462 path = join(self.suite.dir, path)
442 if resolve and self.mustExist and not exists(path): 463 if resolve and self.mustExist and not exists(path):
466 return deps 487 return deps
467 deps.append(self) 488 deps.append(self)
468 return deps 489 return deps
469 490
470 class Suite: 491 class Suite:
471 def __init__(self, d, primary): 492 def __init__(self, d, mxDir, primary):
472 self.dir = d 493 self.dir = d
494 self.mxDir = mxDir
473 self.projects = [] 495 self.projects = []
474 self.libs = [] 496 self.libs = []
475 self.dists = [] 497 self.dists = []
476 self.includes = [] 498 self.includes = []
477 self.commands = None 499 self.commands = None
478 self.primary = primary 500 self.primary = primary
479 mxDir = join(d, 'mx')
480 self._load_env(mxDir) 501 self._load_env(mxDir)
481 self._load_commands(mxDir) 502 self._load_commands(mxDir)
482 self._load_includes(mxDir) 503 self._load_includes(mxDir)
483 self.name = d # re-initialized in _load_projects 504 self.name = d # re-initialized in _load_projects
484 505
588 if not hasattr(mod, 'mx_init'): 609 if not hasattr(mod, 'mx_init'):
589 abort(commandsPath + ' must define an mx_init(env) function') 610 abort(commandsPath + ' must define an mx_init(env) function')
590 if hasattr(mod, 'mx_post_parse_cmd_line'): 611 if hasattr(mod, 'mx_post_parse_cmd_line'):
591 self.mx_post_parse_cmd_line = mod.mx_post_parse_cmd_line 612 self.mx_post_parse_cmd_line = mod.mx_post_parse_cmd_line
592 613
593 mod.mx_init() 614 mod.mx_init(self)
594 self.commands = mod 615 self.commands = mod
595 616
596 def _load_includes(self, mxDir): 617 def _load_includes(self, mxDir):
597 includes = join(mxDir, 'includes') 618 includes = join(mxDir, 'includes')
598 if exists(includes): 619 if exists(includes):
599 with open(includes) as f: 620 with open(includes) as f:
600 for line in f: 621 for line in f:
601 include = expandvars_in_property(line.strip()) 622 include = expandvars_in_property(line.strip())
602 self.includes.append(include) 623 self.includes.append(include)
603 _loadSuite(include, False) 624 _loadSuite(os.path.abspath(include), False)
604 625
605 def _load_env(self, mxDir): 626 def _load_env(self, mxDir):
606 e = join(mxDir, 'env') 627 e = join(mxDir, 'env')
607 if exists(e): 628 if exists(e):
608 with open(e) as f: 629 with open(e) as f:
614 if not '=' in line: 635 if not '=' in line:
615 abort(e + ':' + str(lineNum) + ': line does not match pattern "key=value"') 636 abort(e + ':' + str(lineNum) + ': line does not match pattern "key=value"')
616 key, value = line.split('=', 1) 637 key, value = line.split('=', 1)
617 os.environ[key.strip()] = expandvars_in_property(value.strip()) 638 os.environ[key.strip()] = expandvars_in_property(value.strip())
618 def _post_init(self, opts): 639 def _post_init(self, opts):
619 mxDir = join(self.dir, 'mx') 640 self._load_projects(self.mxDir)
620 self._load_projects(mxDir)
621 for p in self.projects: 641 for p in self.projects:
622 existing = _projects.get(p.name) 642 existing = _projects.get(p.name)
623 if existing is not None: 643 if existing is not None:
624 abort('cannot override project ' + p.name + ' in ' + p.dir + " with project of the same name in " + existing.dir) 644 abort('cannot override project ' + p.name + ' in ' + p.dir + " with project of the same name in " + existing.dir)
625 if not p.name in _opts.ignored_projects: 645 if not p.name in _opts.ignored_projects:
626 _projects[p.name] = p 646 _projects[p.name] = p
627 for l in self.libs: 647 for l in self.libs:
628 existing = _libs.get(l.name) 648 existing = _libs.get(l.name)
629 if existing is not None: 649 # Check that suites that define same library are consistent
630 abort('cannot redefine library ' + l.name) 650 if existing is not None and existing != l:
651 abort('inconsistent library redefinition of ' + l.name + ' in ' + existing.suite.dir + ' and ' + l.suite.dir)
631 _libs[l.name] = l 652 _libs[l.name] = l
632 for d in self.dists: 653 for d in self.dists:
633 existing = _dists.get(d.name) 654 existing = _dists.get(d.name)
634 if existing is not None: 655 if existing is not None:
635 abort('cannot redefine distribution ' + d.name) 656 abort('cannot redefine distribution ' + d.name)
727 return 'windows' 748 return 'windows'
728 else: 749 else:
729 abort('Unknown operating system ' + sys.platform) 750 abort('Unknown operating system ' + sys.platform)
730 751
731 def _loadSuite(d, primary=False): 752 def _loadSuite(d, primary=False):
732 mxDir = join(d, 'mx') 753 """
733 if not exists(mxDir) or not isdir(mxDir): 754 Load a suite from the 'mx' or 'mxbbb' subdirectory of d, where 'bbb' is basename of d
755 """
756 mxDefaultDir = join(d, 'mx')
757 name = os.path.basename(d)
758 mxTaggedDir = mxDefaultDir + name
759 mxDir = None
760 if exists(mxTaggedDir) and isdir(mxTaggedDir):
761 mxDir = mxTaggedDir
762 else:
763 if exists(mxDefaultDir) and isdir(mxDefaultDir):
764 mxDir = mxDefaultDir
765
766
767 if mxDir is None:
734 return None 768 return None
735 if len([s for s in _suites.itervalues() if s.dir == d]) == 0: 769 if len([s for s in _suites.itervalues() if s.dir == d]) == 0:
736 s = Suite(d, primary) 770 s = Suite(d, mxDir, primary)
737 _suites[s.name] = s 771 _suites[name] = s
738 return s 772 return s
739 773
740 def suites(): 774 def suites():
741 """ 775 """
742 Get the list of all loaded suites. 776 Get the list of all loaded suites.
877 are before the projects that depend on them. Unless 'includeLibs' is 911 are before the projects that depend on them. Unless 'includeLibs' is
878 true, libraries are omitted from the result. 912 true, libraries are omitted from the result.
879 """ 913 """
880 deps = [] 914 deps = []
881 if projectNames is None: 915 if projectNames is None:
882 projects = _projects.values() 916 projects = opt_limit_to_suite(_projects.values())
883 else: 917 else:
884 projects = [project(name) for name in projectNames] 918 projects = [project(name) for name in projectNames]
885 919
886 for p in projects: 920 for p in projects:
887 p.all_deps(deps, includeLibs=includeLibs, includeAnnotationProcessors=includeAnnotationProcessors) 921 p.all_deps(deps, includeLibs=includeLibs, includeAnnotationProcessors=includeAnnotationProcessors)
888 return deps 922 return deps
923
924 def opt_limit_to_suite(projects):
925 if _opts.specific_suite is None:
926 return projects
927 else:
928 result = []
929 for p in projects:
930 s = p.suite
931 if s.name == _opts.specific_suite:
932 result.append(p)
933 return result
889 934
890 def _handle_missing_java_home(): 935 def _handle_missing_java_home():
891 if not sys.stdout.isatty(): 936 if not sys.stdout.isatty():
892 abort('Could not find bootstrap JDK. Use --java-home option or ensure JAVA_HOME environment variable is set.') 937 abort('Could not find bootstrap JDK. Use --java-home option or ensure JAVA_HOME environment variable is set.')
893 938
934 return ArgumentParser.format_help(self) + _format_commands() 979 return ArgumentParser.format_help(self) + _format_commands()
935 980
936 981
937 def __init__(self): 982 def __init__(self):
938 self.java_initialized = False 983 self.java_initialized = False
939 ArgumentParser.__init__(self, prog='mx') 984 # this doesn't resolve the right way, but can't figure out how to override _handle_conflict_resolve in _ActionsContainer
985 ArgumentParser.__init__(self, prog='mx', conflict_handler='resolve')
940 986
941 self.add_argument('-v', action='store_true', dest='verbose', help='enable verbose output') 987 self.add_argument('-v', action='store_true', dest='verbose', help='enable verbose output')
942 self.add_argument('-V', action='store_true', dest='very_verbose', help='enable very verbose output') 988 self.add_argument('-V', action='store_true', dest='very_verbose', help='enable very verbose output')
943 self.add_argument('--dbg', type=int, dest='java_dbg_port', help='make Java processes wait on <port> for a debugger', metavar='<port>') 989 self.add_argument('--dbg', type=int, dest='java_dbg_port', help='make Java processes wait on <port> for a debugger', metavar='<port>')
944 self.add_argument('-d', action='store_const', const=8000, dest='java_dbg_port', help='alias for "-dbg 8000"') 990 self.add_argument('-d', action='store_const', const=8000, dest='java_dbg_port', help='alias for "-dbg 8000"')
948 self.add_argument('--Jp', action='append', dest='java_args_pfx', help='prefix Java VM arguments (e.g. --Jp @-dsa)', metavar='@<args>', default=[]) 994 self.add_argument('--Jp', action='append', dest='java_args_pfx', help='prefix Java VM arguments (e.g. --Jp @-dsa)', metavar='@<args>', default=[])
949 self.add_argument('--Ja', action='append', dest='java_args_sfx', help='suffix Java VM arguments (e.g. --Ja @-dsa)', metavar='@<args>', default=[]) 995 self.add_argument('--Ja', action='append', dest='java_args_sfx', help='suffix Java VM arguments (e.g. --Ja @-dsa)', metavar='@<args>', default=[])
950 self.add_argument('--user-home', help='users home directory', metavar='<path>', default=os.path.expanduser('~')) 996 self.add_argument('--user-home', help='users home directory', metavar='<path>', default=os.path.expanduser('~'))
951 self.add_argument('--java-home', help='bootstrap JDK installation directory (must be JDK 6 or later)', metavar='<path>') 997 self.add_argument('--java-home', help='bootstrap JDK installation directory (must be JDK 6 or later)', metavar='<path>')
952 self.add_argument('--ignore-project', action='append', dest='ignored_projects', help='name of project to ignore', metavar='<name>', default=[]) 998 self.add_argument('--ignore-project', action='append', dest='ignored_projects', help='name of project to ignore', metavar='<name>', default=[])
999 self.add_argument('--suite', dest='specific_suite', help='limit command to given suite', default=None)
953 if get_os() != 'windows': 1000 if get_os() != 'windows':
954 # Time outs are (currently) implemented with Unix specific functionality 1001 # Time outs are (currently) implemented with Unix specific functionality
955 self.add_argument('--timeout', help='timeout (in seconds) for command', type=int, default=0, metavar='<secs>') 1002 self.add_argument('--timeout', help='timeout (in seconds) for command', type=int, default=0, metavar='<secs>')
956 self.add_argument('--ptimeout', help='timeout (in seconds) for subprocesses', type=int, default=0, metavar='<secs>') 1003 self.add_argument('--ptimeout', help='timeout (in seconds) for subprocesses', type=int, default=0, metavar='<secs>')
957 1004
984 1031
985 opts.ignored_projects = opts.ignored_projects + os.environ.get('IGNORED_PROJECTS', '').split(',') 1032 opts.ignored_projects = opts.ignored_projects + os.environ.get('IGNORED_PROJECTS', '').split(',')
986 1033
987 commandAndArgs = opts.__dict__.pop('commandAndArgs') 1034 commandAndArgs = opts.__dict__.pop('commandAndArgs')
988 return opts, commandAndArgs 1035 return opts, commandAndArgs
1036
1037 def _handle_conflict_resolve(self, action, conflicting_actions):
1038 self._handle_conflict_error(action, conflicting_actions)
989 1039
990 def _format_commands(): 1040 def _format_commands():
991 msg = '\navailable commands:\n\n' 1041 msg = '\navailable commands:\n\n'
992 for cmd in sorted(_commands.iterkeys()): 1042 for cmd in sorted(_commands.iterkeys()):
993 c, _ = _commands[cmd][:2] 1043 c, _ = _commands[cmd][:2]
1995 javafilelist += [join(root, name) for name in files if name.endswith('.java') and name != 'package-info.java'] 2045 javafilelist += [join(root, name) for name in files if name.endswith('.java') and name != 'package-info.java']
1996 if len(javafilelist) == 0: 2046 if len(javafilelist) == 0:
1997 logv('[no Java sources in {0} - skipping]'.format(sourceDir)) 2047 logv('[no Java sources in {0} - skipping]'.format(sourceDir))
1998 continue 2048 continue
1999 2049
2000 timestampFile = join(p.suite.dir, 'mx', 'checkstyle-timestamps', sourceDir[len(p.suite.dir) + 1:].replace(os.sep, '_') + '.timestamp') 2050 timestampFile = join(p.suite.mxDir, 'checkstyle-timestamps', sourceDir[len(p.suite.dir) + 1:].replace(os.sep, '_') + '.timestamp')
2001 if not exists(dirname(timestampFile)): 2051 if not exists(dirname(timestampFile)):
2002 os.makedirs(dirname(timestampFile)) 2052 os.makedirs(dirname(timestampFile))
2003 mustCheck = False 2053 mustCheck = False
2004 if not args.force and exists(timestampFile): 2054 if not args.force and exists(timestampFile):
2005 timestamp = os.path.getmtime(timestampFile) 2055 timestamp = os.path.getmtime(timestampFile)
2081 if name == 'file': 2131 if name == 'file':
2082 source[0] = attrs['name'] 2132 source[0] = attrs['name']
2083 elif name == 'error': 2133 elif name == 'error':
2084 errors.append('{}:{}: {}'.format(source[0], attrs['line'], attrs['message'])) 2134 errors.append('{}:{}: {}'.format(source[0], attrs['line'], attrs['message']))
2085 2135
2086 p = xml.parsers.expat.ParserCreate() 2136 xp = xml.parsers.expat.ParserCreate()
2087 p.StartElementHandler = start_element 2137 xp.StartElementHandler = start_element
2088 with open(auditfileName) as fp: 2138 with open(auditfileName) as fp:
2089 p.ParseFile(fp) 2139 xp.ParseFile(fp)
2090 if len(errors) != 0: 2140 if len(errors) != 0:
2091 map(log, errors) 2141 map(log, errors)
2092 totalErrors = totalErrors + len(errors) 2142 totalErrors = totalErrors + len(errors)
2093 else: 2143 else:
2094 if exists(timestampFile): 2144 if exists(timestampFile):
2354 out.element('classpathentry', {'exported' : 'true', 'kind' : 'con', 'path' : getattr(dep, 'eclipse.container')}) 2404 out.element('classpathentry', {'exported' : 'true', 'kind' : 'con', 'path' : getattr(dep, 'eclipse.container')})
2355 elif hasattr(dep, 'eclipse.project'): 2405 elif hasattr(dep, 'eclipse.project'):
2356 out.element('classpathentry', {'combineaccessrules' : 'false', 'exported' : 'true', 'kind' : 'src', 'path' : '/' + getattr(dep, 'eclipse.project')}) 2406 out.element('classpathentry', {'combineaccessrules' : 'false', 'exported' : 'true', 'kind' : 'src', 'path' : '/' + getattr(dep, 'eclipse.project')})
2357 else: 2407 else:
2358 path = dep.path 2408 path = dep.path
2359 if dep.mustExist: 2409 dep.get_path(resolve=True)
2360 dep.get_path(resolve=True) 2410 if not exists(path) and not dep.mustExist:
2361 if not isabs(path): 2411 continue
2362 # Relative paths for "lib" class path entries have various semantics depending on the Eclipse 2412
2363 # version being used (e.g. see https://bugs.eclipse.org/bugs/show_bug.cgi?id=274737) so it's 2413 if not isabs(path):
2364 # safest to simply use absolute paths. 2414 # Relative paths for "lib" class path entries have various semantics depending on the Eclipse
2365 path = join(suite.dir, path) 2415 # version being used (e.g. see https://bugs.eclipse.org/bugs/show_bug.cgi?id=274737) so it's
2366 2416 # safest to simply use absolute paths.
2367 attributes = {'exported' : 'true', 'kind' : 'lib', 'path' : path} 2417 path = join(suite.dir, path)
2368 2418
2369 sourcePath = dep.get_source_path(resolve=True) 2419 attributes = {'exported' : 'true', 'kind' : 'lib', 'path' : path}
2370 if sourcePath is not None: 2420
2371 attributes['sourcepath'] = sourcePath 2421 sourcePath = dep.get_source_path(resolve=True)
2372 out.element('classpathentry', attributes) 2422 if sourcePath is not None:
2423 attributes['sourcepath'] = sourcePath
2424 out.element('classpathentry', attributes)
2373 else: 2425 else:
2374 out.element('classpathentry', {'combineaccessrules' : 'false', 'exported' : 'true', 'kind' : 'src', 'path' : '/' + dep.name}) 2426 out.element('classpathentry', {'combineaccessrules' : 'false', 'exported' : 'true', 'kind' : 'src', 'path' : '/' + dep.name})
2375 2427
2376 out.element('classpathentry', {'kind' : 'output', 'path' : getattr(p, 'eclipse.output', 'bin')}) 2428 out.element('classpathentry', {'kind' : 'output', 'path' : getattr(p, 'eclipse.output', 'bin')})
2377 out.close('classpath') 2429 out.close('classpath')
2452 2504
2453 settingsDir = join(p.dir, ".settings") 2505 settingsDir = join(p.dir, ".settings")
2454 if not exists(settingsDir): 2506 if not exists(settingsDir):
2455 os.mkdir(settingsDir) 2507 os.mkdir(settingsDir)
2456 2508
2457 eclipseSettingsDir = join(suite.dir, 'mx', 'eclipse-settings') 2509 eclipseSettingsDir = join(suite.mxDir, 'eclipse-settings')
2458 if exists(eclipseSettingsDir): 2510 if exists(eclipseSettingsDir):
2459 for name in os.listdir(eclipseSettingsDir): 2511 for name in os.listdir(eclipseSettingsDir):
2460 if name == "org.eclipse.jdt.apt.core.prefs" and not len(p.annotation_processors()) > 0: 2512 if name == "org.eclipse.jdt.apt.core.prefs" and not len(p.annotation_processors()) > 0:
2461 continue 2513 continue
2462 path = join(eclipseSettingsDir, name) 2514 path = join(eclipseSettingsDir, name)
2933 if len(indicators) != 0: 2985 if len(indicators) != 0:
2934 if not sys.stdout.isatty() or ask_yes_no(currentDir + ' looks like a removed project -- delete it', 'n'): 2986 if not sys.stdout.isatty() or ask_yes_no(currentDir + ' looks like a removed project -- delete it', 'n'):
2935 shutil.rmtree(currentDir) 2987 shutil.rmtree(currentDir)
2936 log('Deleted ' + currentDir) 2988 log('Deleted ' + currentDir)
2937 2989
2938 def javadoc(args, parser=None, docDir='javadoc', includeDeps=True): 2990 def javadoc(args, parser=None, docDir='javadoc', includeDeps=True, stdDoclet=True):
2939 """generate javadoc for some/all Java projects""" 2991 """generate javadoc for some/all Java projects"""
2940 2992
2941 parser = ArgumentParser(prog='mx javadoc') if parser is None else parser 2993 parser = ArgumentParser(prog='mx javadoc') if parser is None else parser
2942 parser.add_argument('-d', '--base', action='store', help='base directory for output') 2994 parser.add_argument('-d', '--base', action='store', help='base directory for output')
2943 parser.add_argument('--unified', action='store_true', help='put javadoc in a single directory instead of one per project') 2995 parser.add_argument('--unified', action='store_true', help='put javadoc in a single directory instead of one per project')
3035 print >> fp, '<html><body>Documentation for the <code>' + p.name + '</code> project.</body></html>' 3087 print >> fp, '<html><body>Documentation for the <code>' + p.name + '</code> project.</body></html>'
3036 delOverviewFile = True 3088 delOverviewFile = True
3037 nowarnAPI = [] 3089 nowarnAPI = []
3038 if not args.warnAPI: 3090 if not args.warnAPI:
3039 nowarnAPI.append('-XDignore.symbol.file') 3091 nowarnAPI.append('-XDignore.symbol.file')
3092
3093 # windowTitle onloy applies to the standard doclet processor
3094 windowTitle = []
3095 if stdDoclet:
3096 windowTitle = ['-windowtitle', p.name + ' javadoc']
3040 try: 3097 try:
3041 log('Generating {2} for {0} in {1}'.format(p.name, out, docDir)) 3098 log('Generating {2} for {0} in {1}'.format(p.name, out, docDir))
3042 run([java().javadoc, memory, 3099 run([java().javadoc, memory,
3043 '-windowtitle', p.name + ' javadoc',
3044 '-XDignore.symbol.file', 3100 '-XDignore.symbol.file',
3045 '-classpath', cp, 3101 '-classpath', cp,
3046 '-quiet', 3102 '-quiet',
3047 '-d', out, 3103 '-d', out,
3048 '-overview', overviewFile, 3104 '-overview', overviewFile,
3049 '-sourcepath', sp] + 3105 '-sourcepath', sp] +
3050 links + 3106 links +
3051 extraArgs + 3107 extraArgs +
3052 nowarnAPI + 3108 nowarnAPI +
3109 windowTitle +
3053 list(pkgs)) 3110 list(pkgs))
3054 log('Generated {2} for {0} in {1}'.format(p.name, out, docDir)) 3111 log('Generated {2} for {0} in {1}'.format(p.name, out, docDir))
3055 finally: 3112 finally:
3056 if delOverviewFile: 3113 if delOverviewFile:
3057 os.remove(overviewFile) 3114 os.remove(overviewFile)
3435 """ 3492 """
3436 Define how a single command-line argument. 3493 Define how a single command-line argument.
3437 """ 3494 """
3438 assert _argParser is not None 3495 assert _argParser is not None
3439 _argParser.add_argument(*args, **kwargs) 3496 _argParser.add_argument(*args, **kwargs)
3497
3498 def update_commands(suite, new_commands):
3499 for key, value in new_commands.iteritems():
3500 if _commands.has_key(key) and not suite.primary:
3501 pass
3502 # print("WARNING: attempt to redefine command '" + key + "' in suite " + suite.dir)
3503 else:
3504 _commands[key] = value
3440 3505
3441 # Table of commands in alphabetical order. 3506 # Table of commands in alphabetical order.
3442 # Keys are command names, value are lists: [<function>, <usage msg>, <format args to doc string of function>...] 3507 # Keys are command names, value are lists: [<function>, <usage msg>, <format args to doc string of function>...]
3443 # If any of the format args are instances of Callable, then they are called with an 'env' are before being 3508 # If any of the format args are instances of Callable, then they are called with an 'env' are before being
3444 # used in the call to str.format(). 3509 # used in the call to str.format().
3467 3532
3468 _argParser = ArgParser() 3533 _argParser = ArgParser()
3469 3534
3470 def _findPrimarySuite(): 3535 def _findPrimarySuite():
3471 def is_suite_dir(d): 3536 def is_suite_dir(d):
3472 mxDir = join(d, 'mx') 3537 for f in os.listdir(d):
3473 if exists(mxDir) and isdir(mxDir) and exists(join(mxDir, 'projects')): 3538 if fnmatch.fnmatch(f, 'mx*'):
3474 return dirname(mxDir) 3539 mxDir = join(d, f)
3540 if exists(mxDir) and isdir(mxDir) and exists(join(mxDir, 'projects')):
3541 return dirname(mxDir)
3542
3475 3543
3476 # try current working directory first 3544 # try current working directory first
3477 if is_suite_dir(os.getcwd()): 3545 if is_suite_dir(os.getcwd()):
3478 return os.getcwd() 3546 return os.getcwd()
3479 3547