changeset 21976:36e37644f91e

mx: improve first usage experience: Create mx.ask_persist_env to handle env file modifications, make it deal with files missing final newline Use mx.ask_persist_env to persist the DEFAULT_VM Make mx_graal specify its version restrictions when asking for the default JDK When selecting versions manually or automatically, use JDKs from JAVA_HOME first, then from EXTRA_JAVA_HOMES, then from standard locations, then sort by version. Even if EXTRA_JAVA_HOMES is already defained, let the user decide which JAVA_HOME to use
author Gilles Duboscq <gilles.m.duboscq@oracle.com>
date Fri, 12 Jun 2015 16:51:32 +0200
parents 290a87b718e1
children a6425aa8f70c
files mx/mx_graal.py mxtool/mx.py
diffstat 2 files changed, 121 insertions(+), 83 deletions(-) [+]
line wrap: on
line diff
--- a/mx/mx_graal.py	Thu Jun 11 16:17:16 2015 +0200
+++ b/mx/mx_graal.py	Fri Jun 12 16:51:32 2015 +0200
@@ -137,9 +137,7 @@
         items = [k for k in _vmChoices.keys() if _vmChoices[k] is not None]
         descriptions = [_vmChoices[k] for k in _vmChoices.keys() if _vmChoices[k] is not None]
         vm = mx.select_items(items, descriptions, allowMultiple=False)
-        if mx.ask_yes_no('Persist this choice by adding "DEFAULT_VM=' + vm + '" to ' + envPath, 'y'):
-            with open(envPath, 'a') as fp:
-                print >> fp, 'DEFAULT_VM=' + vm
+        mx.ask_persist_env('DEFAULT_VM', vm)
     _vm = vm
     return vm
 
@@ -1808,7 +1806,7 @@
     v8 = mx.VersionSpec("1.8")
     def _igvJdkVersionCheck(version):
         return version >= v8 and (version < v8u20 or version >= v8u40)
-    return mx.java_version(_igvJdkVersionCheck, versionDescription='>= 1.8 and < 1.8.0u20 or >= 1.8.0u40', purpose="building & running IGV").jdk
+    return mx.java(_igvJdkVersionCheck, versionDescription='>= 1.8 and < 1.8.0u20 or >= 1.8.0u40', purpose="building & running IGV").jdk
 
 def _igvBuildEnv():
         # When the http_proxy environment variable is set, convert it to the proxy settings that ant needs
@@ -2617,10 +2615,12 @@
 
 def mx_post_parse_cmd_line(opts):  #
     # TODO _minVersion check could probably be part of a Suite in mx?
-    if mx.java().version < _minVersion:
-        mx.abort('Requires Java version ' + str(_minVersion) + ' or greater for JAVA_HOME, got version ' + str(mx.java().version))
-    if _untilVersion and mx.java().version >= _untilVersion:
-        mx.abort('Requires Java version strictly before ' + str(_untilVersion) + ' for JAVA_HOME, got version ' + str(mx.java().version))
+    def _versionCheck(version):
+        return version >= _minVersion and (not _untilVersion or version >= _untilVersion)
+    versionDesc = ">=" + str(_minVersion)
+    if _untilVersion:
+        versionDesc += " and <=" + str(_untilVersion)
+    mx.java(_versionCheck, versionDescription=versionDesc, defaultJdk=True)
 
     if _vmSourcesAvailable:
         if hasattr(opts, 'vm') and opts.vm is not None:
--- a/mxtool/mx.py	Thu Jun 11 16:17:16 2015 +0200
+++ b/mxtool/mx.py	Fri Jun 12 16:51:32 2015 +0200
@@ -1787,64 +1787,58 @@
 
 _canceled_java_requests = set()
 
-def java(requiredCompliance=None, purpose=None, cancel=None):
+def java(versionCheck=None, purpose=None, cancel=None, versionDescription=None, defaultJdk=None):
     """
     Get a JavaConfig object containing Java commands launch details.
-    If requiredCompliance is None, the compliance level specified by --java-home/JAVA_HOME
-    is returned. Otherwise, the JavaConfig exactly matching requiredCompliance is returned
-    or None if there is no exact match.
     """
 
-    global _default_java_home
-    if cancel and (requiredCompliance, purpose) in _canceled_java_requests:
+    if defaultJdk is None:
+        defaultJdk = versionCheck is None and not purpose
+
+    # interpret string and compliance as compliance check
+    if isinstance(versionCheck, types.StringTypes):
+        requiredCompliance = JavaCompliance(versionCheck)
+        versionCheck, versionDescription = _convert_complicance_to_version_check(requiredCompliance)
+    elif isinstance(versionCheck, JavaCompliance):
+        versionCheck, versionDescription = _convert_complicance_to_version_check(versionCheck)
+
+    global _default_java_home, _extra_java_homes
+    if cancel and (versionDescription, purpose) in _canceled_java_requests:
         return None
 
-    if not requiredCompliance:
+    if defaultJdk:
         if not _default_java_home:
-            _default_java_home = _find_jdk(purpose=purpose, cancel=cancel)
+            _default_java_home = _find_jdk(versionCheck=versionCheck, versionDescription=versionDescription, purpose=purpose, cancel=cancel, isDefaultJdk=True)
             if not _default_java_home:
-                assert cancel
-                _canceled_java_requests.add((requiredCompliance, purpose))
+                assert cancel and (versionDescription or purpose)
+                _canceled_java_requests.add((versionDescription, purpose))
         return _default_java_home
 
-    if _opts.strict_compliance:
-        complianceCheck = requiredCompliance.exactMatch
-        desc = str(requiredCompliance)
-    else:
-        compVersion = VersionSpec(str(requiredCompliance))
-        complianceCheck = lambda version: version >= compVersion
-        desc = '>=' + str(requiredCompliance)
-
     for java in _extra_java_homes:
-        if complianceCheck(java.version):
+        if not versionCheck or versionCheck(java.version):
             return java
 
-    jdk = _find_jdk(versionCheck=complianceCheck, versionDescription=desc, purpose=purpose, cancel=cancel)
+    jdk = _find_jdk(versionCheck=versionCheck, versionDescription=versionDescription, purpose=purpose, cancel=cancel, isDefaultJdk=False)
     if jdk:
         assert jdk not in _extra_java_homes
-        _extra_java_homes.append(jdk)
+        _extra_java_homes = _sorted_unique_jdk_configs(_extra_java_homes + [jdk])
     else:
-        assert cancel
-        _canceled_java_requests.add((requiredCompliance, purpose))
+        assert cancel and (versionDescription or purpose)
+        _canceled_java_requests.add((versionDescription, purpose))
     return jdk
 
-def java_version(versionCheck, versionDescription=None, purpose=None):
-    if _default_java_home and versionCheck(_default_java_home.version):
-        return _default_java_home
-    for java in _extra_java_homes:
-        if versionCheck(java.version):
-            return java
-    jdk = _find_jdk(versionCheck, versionDescription, purpose)
-    assert jdk not in _extra_java_homes
-    _extra_java_homes.append(jdk)
-    return jdk
-
-def _find_jdk(versionCheck=None, versionDescription=None, purpose=None, cancel=None):
-    assert not versionDescription or versionCheck
-    if not versionCheck and not purpose:
-        isDefaultJdk = True
+def _convert_complicance_to_version_check(requiredCompliance):
+    if _opts.strict_compliance:
+        versionDesc = str(requiredCompliance)
+        versionCheck = requiredCompliance.exactMatch
     else:
-        isDefaultJdk = False
+        versionDesc = '>=' + str(requiredCompliance)
+        compVersion = VersionSpec(str(requiredCompliance))
+        versionCheck = lambda version: version >= compVersion
+    return (versionCheck, versionDesc)
+
+def _find_jdk(versionCheck=None, versionDescription=None, purpose=None, cancel=None, isDefaultJdk=False):
+    assert (versionDescription and versionCheck) or (not versionDescription and not versionCheck)
     if not versionCheck:
         versionCheck = lambda v: True
 
@@ -1872,35 +1866,16 @@
 
     result = _find_jdk_in_candidates(candidateJdks, versionCheck, warn=True, source=source)
     if not result:
-        candidateJdks = []
-        source = ''
-
-        if get_os() == 'darwin':
-            base = '/Library/Java/JavaVirtualMachines'
-            if exists(base):
-                candidateJdks = [join(base, n, 'Contents/Home') for n in os.listdir(base)]
-        elif get_os() == 'linux':
-            base = '/usr/lib/jvm'
-            if exists(base):
-                candidateJdks = [join(base, n) for n in os.listdir(base)]
-            base = '/usr/java'
-            if exists(base):
-                candidateJdks += [join(base, n) for n in os.listdir(base)]
-        elif get_os() == 'solaris':
-            base = '/usr/jdk/instances'
-            if exists(base):
-                candidateJdks = [join(base, n) for n in os.listdir(base)]
-        elif get_os() == 'windows':
-            base = r'C:\Program Files\Java'
-            if exists(base):
-                candidateJdks = [join(base, n) for n in os.listdir(base)]
-
-        configs = _filtered_jdk_configs(candidateJdks, versionCheck)
+        configs = _find_available_jdks(versionCheck)
+    elif isDefaultJdk:  # we found something in EXTRA_JAVA_HOMES but we want to set JAVA_HOME, look for further options
+        configs = [result] + _find_available_jdks(versionCheck)
     else:
         if not isDefaultJdk:
             return result
         configs = [result]
 
+    configs = _sorted_unique_jdk_configs(configs)
+
     if len(configs) > 1:
         if not is_interactive():
             msg = "Multiple possible choices for a JDK"
@@ -1926,6 +1901,7 @@
             choices = configs + ['<other>']
             if cancel:
                 choices.append('Cancel (' + cancel + ')')
+
             selected = select_items(choices, allowMultiple=False)
             if isinstance(selected, types.StringTypes) and selected == '<other>':
                 selected = None
@@ -1955,11 +1931,22 @@
     while not selected:
         jdkLocation = raw_input('Enter path of JDK: ')
         selected = _find_jdk_in_candidates([jdkLocation], versionCheck, warn=True)
+        if not selected:
+            assert versionDescription
+            log("Error: JDK at '" + jdkLocation + "' is not compatible with version " + versionDescription)
 
     varName = 'JAVA_HOME' if isDefaultJdk else 'EXTRA_JAVA_HOMES'
     allowMultiple = not isDefaultJdk
+    valueSeparator = os.pathsep if allowMultiple else None
+    ask_persist_env(varName, selected.jdk, valueSeparator)
+
+    os.environ[varName] = selected.jdk
+
+    return selected
+
+def ask_persist_env(varName, value, valueSeparator=None):
     envPath = join(_primary_suite.mxDir, 'env')
-    if is_interactive() and ask_yes_no('Persist this setting by adding "{0}={1}" to {2}'.format(varName, selected.jdk, envPath), 'y'):
+    if is_interactive() and ask_yes_no('Persist this setting by adding "{0}={1}" to {2}'.format(varName, value, envPath), 'y'):
         envLines = []
         if exists(envPath):
             with open(envPath) as fp:
@@ -1968,32 +1955,81 @@
                     if line.rstrip().startswith(varName):
                         _, currentValue = line.split('=', 1)
                         currentValue = currentValue.strip()
-                        if not allowMultiple and currentValue:
-                            if not ask_yes_no('{0} is already set to {1}, overwrite with {2}?'.format(varName, currentValue, selected.jdk), 'n'):
-                                return selected
+                        if not valueSeparator and currentValue:
+                            if not ask_yes_no('{0} is already set to {1}, overwrite with {2}?'.format(varName, currentValue, value), 'n'):
+                                return
                             else:
-                                line = varName + '=' + selected.jdk + os.linesep
+                                line = varName + '=' + value + os.linesep
                         else:
                             line = line.rstrip()
                             if currentValue:
-                                line += os.pathsep
-                            line += selected.jdk + os.linesep
+                                line += valueSeparator
+                            line += value + os.linesep
                         append = False
+                    if not line.endswith(os.linesep):
+                        line += os.linesep
                     envLines.append(line)
         else:
             append = True
 
         if append:
-            envLines.append(varName + '=' + selected.jdk + os.linesep)
+            envLines.append(varName + '=' + value + os.linesep)
 
         with open(envPath, 'w') as fp:
             for line in envLines:
                 fp.write(line)
 
-    if varName == 'JAVA_HOME':
-        os.environ['JAVA_HOME'] = selected.jdk
-
-    return selected
+_os_jdk_locations = {
+    'darwin': {
+        'bases': ['/Library/Java/JavaVirtualMachines'],
+        'suffix': 'Contents/Home'
+    },
+    'linux': {
+        'bases': [
+            '/usr/lib/jvm',
+            '/usr/java'
+        ]
+    },
+    'solaris': {
+        'bases': ['/usr/jdk/instances']
+    },
+    'windows': {
+        'bases': [r'C:\Program Files\Java']
+    },
+}
+
+def _find_available_jdks(versionCheck):
+    candidateJdks = []
+    os_name = get_os()
+    if os_name in _os_jdk_locations:
+        jdkLocations = _os_jdk_locations[os_name]
+        for base in jdkLocations['bases']:
+            if exists(base):
+                if 'suffix' in jdkLocations:
+                    suffix = jdkLocations['suffix']
+                    candidateJdks += [join(base, n, suffix) for n in os.listdir(base)]
+                else:
+                    candidateJdks += [join(base, n) for n in os.listdir(base)]
+
+    return _filtered_jdk_configs(candidateJdks, versionCheck)
+
+def _sorted_unique_jdk_configs(configs):
+    path_seen = set()
+    unique_configs = [c for c in configs if c.jdk not in path_seen and not path_seen.add(c.jdk)]
+
+    def _compare_configs(c1, c2):
+        if c1 == _default_java_home:
+            if c2 != _default_java_home:
+                return 1
+        elif c2 == _default_java_home:
+            return -1
+        if c1 in _extra_java_homes:
+            if c2 not in _extra_java_homes:
+                return 1
+        elif c2 in _extra_java_homes:
+            return -1
+        return VersionSpec.__cmp__(c1.version, c2.version)
+    return sorted(unique_configs, cmp=_compare_configs, reverse=True)
 
 def is_interactive():
     return sys.__stdin__.isatty()
@@ -2393,6 +2429,8 @@
         return hash(self.jdk)
 
     def __cmp__(self, other):
+        if other is None:
+            return False
         if isinstance(other, JavaConfig):
             compilanceCmp = cmp(self.javaCompliance, other.javaCompliance)
             if compilanceCmp: