# HG changeset patch # User Mick Jordan # Date 1378926264 -7200 # Node ID 3676540f71cf83bcb4d8a0c628c2f11b342d9724 # Parent 943f1863e1c19e228fae63f3d8c4c9b4781aa9aa Allow mxtool suites to be in separate repositories (CR-1367) diff -r 943f1863e1c1 -r 3676540f71cf mx/commands.py --- a/mx/commands.py Wed Sep 11 16:41:15 2013 +0200 +++ b/mx/commands.py Wed Sep 11 21:04:24 2013 +0200 @@ -474,11 +474,14 @@ mx.log('Running pylint on ' + pyfile + '...') mx.run(['pylint', '--reports=n', '--rcfile=' + rcfile, pyfile], env=env) -def jdkhome(args, vm=None): - """print the JDK directory selected for the 'vm' command""" +def jdkhome(vm=None): + """return the JDK directory selected for the 'vm' command""" + build = _vmbuild if _vmSourcesAvailable else 'product' + return _jdk(build, installGraalJar=False) - build = _vmbuild if _vmSourcesAvailable else 'product' - print _jdk(build, installGraalJar=False) +def print_jdkhome(args, vm=None): + """print the JDK directory selected for the 'vm' command""" + print jdkhome(vm) def buildvars(args): """describe the variables that can be set by the -D option to the 'mx build' commmand""" @@ -1358,7 +1361,7 @@ '--title', 'Graal OpenJDK Project Documentation', '--dot-output-base', 'projects'] + args) -def mx_init(): +def mx_init(suite): commands = { 'build': [build, ''], 'buildvars': [buildvars, ''], @@ -1367,7 +1370,7 @@ 'hsdis': [hsdis, '[att]'], 'hcfdis': [hcfdis, ''], 'igv' : [igv, ''], - 'jdkhome': [jdkhome, ''], + 'jdkhome': [print_jdkhome, ''], 'pylint': [pylint, ''], 'dacapo': [dacapo, '[VM options] benchmarks...|"all" [DaCapo options]'], 'scaladacapo': [scaladacapo, '[VM options] benchmarks...|"all" [Scala DaCapo options]'], @@ -1406,7 +1409,7 @@ 'export': [export, '[-options] [zipfile]'], }) - mx._commands.update(commands) + mx.update_commands(suite, commands) def mx_post_parse_cmd_line(opts): # # TODO _minVersion check could probably be part of a Suite in mx? diff -r 943f1863e1c1 -r 3676540f71cf mxtool/mx.py --- a/mxtool/mx.py Wed Sep 11 16:41:15 2013 +0200 +++ b/mxtool/mx.py Wed Sep 11 21:04:24 2013 +0200 @@ -37,7 +37,11 @@ containing one or more projects. It's not coincidental that this closely matches the layout of one or more projects in a Mercurial repository. The configuration information for a suite lives in an 'mx' sub-directory -at the top level of the suite. +at the top level of the suite. A suite is given a name by a 'suite=name' +property in the 'mx/projects' file (if omitted the name is suite directory). +An 'mx' subdirectory can be named as plain 'mx' or 'mxbasename', where +'basename' is the os.path.basename of the suite directory. +The latter is useful to avoid clashes in IDE project names. When launched, mx treats the current working directory as a suite. This is the primary suite. All other suites are called included suites. @@ -137,7 +141,7 @@ Property values can use environment variables with Bash syntax (e.g. ${HOME}). """ -import sys, os, errno, time, subprocess, shlex, types, urllib2, contextlib, StringIO, zipfile, signal, xml.sax.saxutils, tempfile +import sys, os, errno, time, subprocess, shlex, types, urllib2, contextlib, StringIO, zipfile, signal, xml.sax.saxutils, tempfile, fnmatch import textwrap import xml.parsers.expat import shutil, re, xml.dom.minidom @@ -435,6 +439,23 @@ if url.endswith('/') != self.path.endswith(os.sep): abort('Path for dependency directory must have a URL ending with "/": path=' + self.path + ' url=' + url) + def __eq__(self, other): + if isinstance(other, Library): + if len(self.urls) == 0: + return self.path == other.path + else: + return self.urls == other.urls + else: + return NotImplemented + + + def __ne__(self, other): + result = self.__eq__(other) + if result is NotImplemented: + return result + return not result + + def get_path(self, resolve): path = self.path if not isabs(path): @@ -468,15 +489,15 @@ return deps class Suite: - def __init__(self, d, primary): + def __init__(self, d, mxDir, primary): self.dir = d + self.mxDir = mxDir self.projects = [] self.libs = [] self.dists = [] self.includes = [] self.commands = None self.primary = primary - mxDir = join(d, 'mx') self._load_env(mxDir) self._load_commands(mxDir) self._load_includes(mxDir) @@ -590,7 +611,7 @@ if hasattr(mod, 'mx_post_parse_cmd_line'): self.mx_post_parse_cmd_line = mod.mx_post_parse_cmd_line - mod.mx_init() + mod.mx_init(self) self.commands = mod def _load_includes(self, mxDir): @@ -600,7 +621,7 @@ for line in f: include = expandvars_in_property(line.strip()) self.includes.append(include) - _loadSuite(include, False) + _loadSuite(os.path.abspath(include), False) def _load_env(self, mxDir): e = join(mxDir, 'env') @@ -616,8 +637,7 @@ key, value = line.split('=', 1) os.environ[key.strip()] = expandvars_in_property(value.strip()) def _post_init(self, opts): - mxDir = join(self.dir, 'mx') - self._load_projects(mxDir) + self._load_projects(self.mxDir) for p in self.projects: existing = _projects.get(p.name) if existing is not None: @@ -626,8 +646,9 @@ _projects[p.name] = p for l in self.libs: existing = _libs.get(l.name) - if existing is not None: - abort('cannot redefine library ' + l.name) + # Check that suites that define same library are consistent + if existing is not None and existing != l: + abort('inconsistent library redefinition of ' + l.name + ' in ' + existing.suite.dir + ' and ' + l.suite.dir) _libs[l.name] = l for d in self.dists: existing = _dists.get(d.name) @@ -729,12 +750,25 @@ abort('Unknown operating system ' + sys.platform) def _loadSuite(d, primary=False): - mxDir = join(d, 'mx') - if not exists(mxDir) or not isdir(mxDir): + """ + Load a suite from the 'mx' or 'mxbbb' subdirectory of d, where 'bbb' is basename of d + """ + mxDefaultDir = join(d, 'mx') + name = os.path.basename(d) + mxTaggedDir = mxDefaultDir + name + mxDir = None + if exists(mxTaggedDir) and isdir(mxTaggedDir): + mxDir = mxTaggedDir + else: + if exists(mxDefaultDir) and isdir(mxDefaultDir): + mxDir = mxDefaultDir + + + if mxDir is None: return None if len([s for s in _suites.itervalues() if s.dir == d]) == 0: - s = Suite(d, primary) - _suites[s.name] = s + s = Suite(d, mxDir, primary) + _suites[name] = s return s def suites(): @@ -879,7 +913,7 @@ """ deps = [] if projectNames is None: - projects = _projects.values() + projects = opt_limit_to_suite(_projects.values()) else: projects = [project(name) for name in projectNames] @@ -887,6 +921,17 @@ p.all_deps(deps, includeLibs=includeLibs, includeAnnotationProcessors=includeAnnotationProcessors) return deps +def opt_limit_to_suite(projects): + if _opts.specific_suite is None: + return projects + else: + result = [] + for p in projects: + s = p.suite + if s.name == _opts.specific_suite: + result.append(p) + return result + def _handle_missing_java_home(): if not sys.stdout.isatty(): abort('Could not find bootstrap JDK. Use --java-home option or ensure JAVA_HOME environment variable is set.') @@ -936,7 +981,8 @@ def __init__(self): self.java_initialized = False - ArgumentParser.__init__(self, prog='mx') + # this doesn't resolve the right way, but can't figure out how to override _handle_conflict_resolve in _ActionsContainer + ArgumentParser.__init__(self, prog='mx', conflict_handler='resolve') self.add_argument('-v', action='store_true', dest='verbose', help='enable verbose output') self.add_argument('-V', action='store_true', dest='very_verbose', help='enable very verbose output') @@ -950,6 +996,7 @@ self.add_argument('--user-home', help='users home directory', metavar='', default=os.path.expanduser('~')) self.add_argument('--java-home', help='bootstrap JDK installation directory (must be JDK 6 or later)', metavar='') self.add_argument('--ignore-project', action='append', dest='ignored_projects', help='name of project to ignore', metavar='', default=[]) + self.add_argument('--suite', dest='specific_suite', help='limit command to given suite', default=None) if get_os() != 'windows': # Time outs are (currently) implemented with Unix specific functionality self.add_argument('--timeout', help='timeout (in seconds) for command', type=int, default=0, metavar='') @@ -987,6 +1034,9 @@ commandAndArgs = opts.__dict__.pop('commandAndArgs') return opts, commandAndArgs + def _handle_conflict_resolve(self, action, conflicting_actions): + self._handle_conflict_error(action, conflicting_actions) + def _format_commands(): msg = '\navailable commands:\n\n' for cmd in sorted(_commands.iterkeys()): @@ -1997,7 +2047,7 @@ logv('[no Java sources in {0} - skipping]'.format(sourceDir)) continue - timestampFile = join(p.suite.dir, 'mx', 'checkstyle-timestamps', sourceDir[len(p.suite.dir) + 1:].replace(os.sep, '_') + '.timestamp') + timestampFile = join(p.suite.mxDir, 'checkstyle-timestamps', sourceDir[len(p.suite.dir) + 1:].replace(os.sep, '_') + '.timestamp') if not exists(dirname(timestampFile)): os.makedirs(dirname(timestampFile)) mustCheck = False @@ -2083,10 +2133,10 @@ elif name == 'error': errors.append('{}:{}: {}'.format(source[0], attrs['line'], attrs['message'])) - p = xml.parsers.expat.ParserCreate() - p.StartElementHandler = start_element + xp = xml.parsers.expat.ParserCreate() + xp.StartElementHandler = start_element with open(auditfileName) as fp: - p.ParseFile(fp) + xp.ParseFile(fp) if len(errors) != 0: map(log, errors) totalErrors = totalErrors + len(errors) @@ -2356,20 +2406,22 @@ out.element('classpathentry', {'combineaccessrules' : 'false', 'exported' : 'true', 'kind' : 'src', 'path' : '/' + getattr(dep, 'eclipse.project')}) else: path = dep.path - if dep.mustExist: - dep.get_path(resolve=True) - if not isabs(path): - # Relative paths for "lib" class path entries have various semantics depending on the Eclipse - # version being used (e.g. see https://bugs.eclipse.org/bugs/show_bug.cgi?id=274737) so it's - # safest to simply use absolute paths. - path = join(suite.dir, path) - - attributes = {'exported' : 'true', 'kind' : 'lib', 'path' : path} - - sourcePath = dep.get_source_path(resolve=True) - if sourcePath is not None: - attributes['sourcepath'] = sourcePath - out.element('classpathentry', attributes) + dep.get_path(resolve=True) + if not exists(path) and not dep.mustExist: + continue + + if not isabs(path): + # Relative paths for "lib" class path entries have various semantics depending on the Eclipse + # version being used (e.g. see https://bugs.eclipse.org/bugs/show_bug.cgi?id=274737) so it's + # safest to simply use absolute paths. + path = join(suite.dir, path) + + attributes = {'exported' : 'true', 'kind' : 'lib', 'path' : path} + + sourcePath = dep.get_source_path(resolve=True) + if sourcePath is not None: + attributes['sourcepath'] = sourcePath + out.element('classpathentry', attributes) else: out.element('classpathentry', {'combineaccessrules' : 'false', 'exported' : 'true', 'kind' : 'src', 'path' : '/' + dep.name}) @@ -2454,7 +2506,7 @@ if not exists(settingsDir): os.mkdir(settingsDir) - eclipseSettingsDir = join(suite.dir, 'mx', 'eclipse-settings') + eclipseSettingsDir = join(suite.mxDir, 'eclipse-settings') if exists(eclipseSettingsDir): for name in os.listdir(eclipseSettingsDir): if name == "org.eclipse.jdt.apt.core.prefs" and not len(p.annotation_processors()) > 0: @@ -2935,7 +2987,7 @@ shutil.rmtree(currentDir) log('Deleted ' + currentDir) -def javadoc(args, parser=None, docDir='javadoc', includeDeps=True): +def javadoc(args, parser=None, docDir='javadoc', includeDeps=True, stdDoclet=True): """generate javadoc for some/all Java projects""" parser = ArgumentParser(prog='mx javadoc') if parser is None else parser @@ -3037,10 +3089,14 @@ nowarnAPI = [] if not args.warnAPI: nowarnAPI.append('-XDignore.symbol.file') + + # windowTitle onloy applies to the standard doclet processor + windowTitle = [] + if stdDoclet: + windowTitle = ['-windowtitle', p.name + ' javadoc'] try: log('Generating {2} for {0} in {1}'.format(p.name, out, docDir)) run([java().javadoc, memory, - '-windowtitle', p.name + ' javadoc', '-XDignore.symbol.file', '-classpath', cp, '-quiet', @@ -3050,6 +3106,7 @@ links + extraArgs + nowarnAPI + + windowTitle + list(pkgs)) log('Generated {2} for {0} in {1}'.format(p.name, out, docDir)) finally: @@ -3438,6 +3495,14 @@ assert _argParser is not None _argParser.add_argument(*args, **kwargs) +def update_commands(suite, new_commands): + for key, value in new_commands.iteritems(): + if _commands.has_key(key) and not suite.primary: + pass + # print("WARNING: attempt to redefine command '" + key + "' in suite " + suite.dir) + else: + _commands[key] = value + # Table of commands in alphabetical order. # Keys are command names, value are lists: [, , ...] # If any of the format args are instances of Callable, then they are called with an 'env' are before being @@ -3469,9 +3534,12 @@ def _findPrimarySuite(): def is_suite_dir(d): - mxDir = join(d, 'mx') - if exists(mxDir) and isdir(mxDir) and exists(join(mxDir, 'projects')): - return dirname(mxDir) + for f in os.listdir(d): + if fnmatch.fnmatch(f, 'mx*'): + mxDir = join(d, f) + if exists(mxDir) and isdir(mxDir) and exists(join(mxDir, 'projects')): + return dirname(mxDir) + # try current working directory first if is_suite_dir(os.getcwd()):