#
# ----------------------------------------------------------------------------------------------------
#
# Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
#
# This code is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License version 2 only, as
# published by the Free Software Foundation.
#
# This code is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
# version 2 for more details (a copy is included in the LICENSE file that
# accompanied this code).
#
# You should have received a copy of the GNU General Public License version
# 2 along with this work; if not, write to the Free Software Foundation,
# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
# or visit www.oracle.com if you need additional information or have any
# questions.
#
# ----------------------------------------------------------------------------------------------------

import os, shutil, zipfile, re, time, sys, datetime, platform
from os.path import join, exists, dirname, isdir
from argparse import ArgumentParser, REMAINDER
import StringIO
import xml.dom.minidom
import subprocess

import mx
import mx_gate
import mx_unittest

from mx_gate import Task
from mx_unittest import unittest

_suite = mx.suite('jvmci')

JVMCI_VERSION = 9

_JVMCI_JDK_TAG = 'jvmci'

_minVersion = mx.VersionSpec('1.9')

# max version (first _unsupported_ version)
_untilVersion = None

_jvmciModes = {
    'hosted' : ['-XX:+UnlockExperimentalVMOptions', '-XX:+EnableJVMCI'],
    'jit' : ['-XX:+UnlockExperimentalVMOptions', '-XX:+EnableJVMCI', '-XX:+UseJVMCICompiler'],
    'disabled' : []
}

# TODO: can optimized be built without overriding release build?
_jdkDebugLevels = ['release', 'fastdebug', 'slowdebug']

# TODO: add client once/if it can be built on 64-bit platforms
_jdkJvmVariants = ['server', 'client']

"""
Translation table from mx_jvmci:8 --vmbuild values to mx_jvmci:9 --jdk-debug-level values.
"""
_legacyVmbuilds = {
    'product' : 'release',
    'debug' : 'slowdebug'
}

"""
Translates a mx_jvmci:8 --vmbuild value to a mx_jvmci:9 --jdk-debug-level value.
"""
def _translateLegacyDebugLevel(debugLevel):
    return _legacyVmbuilds.get(debugLevel, debugLevel)

"""
Translation table from mx_jvmci:8 --vm values to mx_jvmci:9 (--jdk-jvm-variant, --jvmci-mode) tuples.
"""
_legacyVms = {
    'jvmci' : ('server', 'jit')
}

"""
A VM configuration composed of a JDK debug level, JVM variant and a JVMCI mode.
This is also a context manager that can be used with the 'with' statement to set/change
a VM configuration within a dynamic scope. For example:

    with ConfiguredJDK(debugLevel='fastdebug'):
        dacapo(['pmd'])
"""
class VM:
    def __init__(self, jvmVariant=None, debugLevel=None, jvmciMode=None):
        self.update(jvmVariant, debugLevel, jvmciMode)

    def update(self, jvmVariant=None, debugLevel=None, jvmciMode=None):
        if jvmVariant in _legacyVms:
            # Backwards compatibility for mx_jvmci:8 API
            jvmVariant, newJvmciMode = _legacyVms[jvmVariant]
            if jvmciMode is not None and jvmciMode != newJvmciMode:
                mx.abort('JVM variant "' + jvmVariant + '" implies JVMCI mode "' + newJvmciMode +
                         '" which conflicts with explicitly specified JVMCI mode of "' + jvmciMode + '"')
            jvmciMode = newJvmciMode
        debugLevel = _translateLegacyDebugLevel(debugLevel)
        assert jvmVariant is None or jvmVariant in _jdkJvmVariants, jvmVariant
        assert debugLevel is None or debugLevel in _jdkDebugLevels, debugLevel
        assert jvmciMode is None or jvmciMode in _jvmciModes, jvmciMode
        self.jvmVariant = jvmVariant or _vm.jvmVariant
        self.debugLevel = debugLevel or _vm.debugLevel
        self.jvmciMode = jvmciMode or _vm.jvmciMode

    def __enter__(self):
        global _vm
        self.previousVm = _vm
        _vm = self

    def __exit__(self, exc_type, exc_value, traceback):
        global _vm
        _vm = self.previousVm

_vm = VM(jvmVariant=_jdkJvmVariants[0], debugLevel=_jdkDebugLevels[0], jvmciMode='hosted')

def get_vm():
    """
    Gets the configured VM.
    """
    return _vm

def relativeVmLibDirInJdk():
    mxos = mx.get_os()
    if mxos == 'darwin':
        return join('lib')
    if mxos == 'windows' or mxos == 'cygwin':
        return join('bin')
    return join('lib', mx.get_arch())

def isJVMCIEnabled(vm):
    assert vm in _jdkJvmVariants
    return True

def _makehelp():
    return subprocess.check_output([mx.gmake_cmd(), 'help'], cwd=_get_jdk_dir())

def _runmake(args):
    """run the JDK make process

To build hotspot and import it into the JDK: "mx make hotspot import-hotspot"
{0}"""

    jdkBuildDir = _get_jdk_build_dir()
    if not exists(jdkBuildDir):
        # JDK10 must be bootstrapped with a JDK9
        compliance = mx.JavaCompliance('9')
        jdk9 = mx.get_jdk(compliance.exactMatch, versionDescription=compliance.value)
        cmd = ['sh', 'configure', '--with-debug-level=' + _vm.debugLevel, '--with-native-debug-symbols=external', '--disable-precompiled-headers', '--with-jvm-features=graal',
               '--with-jvm-variants=' + _vm.jvmVariant, '--disable-warnings-as-errors', '--with-boot-jdk=' + jdk9.home, '--with-jvm-features=graal']
        mx.run(cmd, cwd=_get_jdk_dir())
    cmd = [mx.gmake_cmd(), 'CONF=' + _vm.debugLevel]
    if mx.get_opts().verbose:
        cmd.append('LOG=debug')
    cmd.extend(args)
    if mx.get_opts().use_jdk_image and 'images' not in args:
        cmd.append('images')

    if not mx.get_opts().verbose:
        mx.log('--------------- make execution ----------------------')
        mx.log('Working directory: ' + _get_jdk_dir())
        mx.log('Command line: ' + ' '.join(cmd))
        mx.log('-----------------------------------------------------')

    mx.run(cmd, cwd=_get_jdk_dir())

def _runmultimake(args):
    """run the JDK make process for one or more configurations"""

    jvmVariantsDefault = ','.join(_jdkJvmVariants)
    debugLevelsDefault = ','.join(_jdkDebugLevels)

    parser = ArgumentParser(prog='mx multimake')
    parser.add_argument('--jdk-jvm-variants', '--vms', help='a comma separated list of VMs to build (default: ' + jvmVariantsDefault + ')', metavar='<args>', default=jvmVariantsDefault)
    parser.add_argument('--jdk-debug-levels', '--builds', help='a comma separated list of JDK debug levels (default: ' + debugLevelsDefault + ')', metavar='<args>', default=debugLevelsDefault)
    parser.add_argument('-n', '--no-check', action='store_true', help='omit running "java -version" after each build')
    select = parser.add_mutually_exclusive_group()
    select.add_argument('-c', '--console', action='store_true', help='send build output to console instead of log files')
    select.add_argument('-d', '--output-dir', help='directory for log files instead of current working directory', default=os.getcwd(), metavar='<dir>')

    args = parser.parse_args(args)
    jvmVariants = args.jdk_jvm_variants.split(',')
    debugLevels = [_translateLegacyDebugLevel(dl) for dl in args.jdk_debug_levels.split(',')]

    allStart = time.time()
    for jvmVariant in jvmVariants:
        for debugLevel in debugLevels:
            if not args.console:
                logFile = join(mx.ensure_dir_exists(args.output_dir), jvmVariant + '-' + debugLevel + '.log')
                log = open(logFile, 'wb')
                start = time.time()
                mx.log('BEGIN: ' + jvmVariant + '-' + debugLevel + '\t(see: ' + logFile + ')')
                verbose = ['-v'] if mx.get_opts().verbose else []
                # Run as subprocess so that output can be directed to a file
                cmd = [sys.executable, '-u', mx.__file__] + verbose + ['--jdk-jvm-variant=' + jvmVariant, '--jdk-debug-level=' + debugLevel, 'make']
                mx.logv("executing command: " + str(cmd))
                subprocess.check_call(cmd, cwd=_suite.dir, stdout=log, stderr=subprocess.STDOUT)
                duration = datetime.timedelta(seconds=time.time() - start)
                mx.log('END:   ' + jvmVariant + '-' + debugLevel + '\t[' + str(duration) + ']')
            else:
                with VM(jvmVariant=jvmVariant, debugLevel=debugLevel):
                    _runmake([])
            if not args.no_check:
                with VM(jvmciMode='jit'):
                    run_vm(['-XX:-BootstrapJVMCI', '-version'])
    allDuration = datetime.timedelta(seconds=time.time() - allStart)
    mx.log('TOTAL TIME:   ' + '[' + str(allDuration) + ']')

class HotSpotProject(mx.NativeProject):
    """
    Defines a NativeProject representing the HotSpot binaries built via make.
    """
    def __init__(self, suite, name, deps, workingSets, **args):
        assert name == 'hotspot'
        mx.NativeProject.__init__(self, suite, name, "", [], deps, workingSets, None, None, join(suite.mxDir, name))

    def eclipse_config_up_to_date(self, configZip):
        # Assume that any change to this module might imply changes to the generated IDE files
        if configZip.isOlderThan(__file__):
            return False
        for _, source in self._get_eclipse_settings_sources().iteritems():
            if configZip.isOlderThan(source):
                return False
        return True

    def _get_eclipse_settings_sources(self):
        """
        Gets a dictionary from the name of an Eclipse settings file to
        the file providing its generated content.
        """
        if not hasattr(self, '_eclipse_settings'):
            esdict = {}
            templateSettingsDir = join(self.dir, 'templates', 'eclipse', 'settings')
            if exists(templateSettingsDir):
                for name in os.listdir(templateSettingsDir):
                    source = join(templateSettingsDir, name)
                    esdict[name] = source
            self._eclipse_settings = esdict
        return self._eclipse_settings

    def _eclipseinit(self, files=None, libFiles=None):
        """
        Generates an Eclipse project for each HotSpot build configuration.
        """

        roots = [
            'cpu',
            'os',
            'os_cpu',
            'share'
        ]

        for jvmVariant in _jdkJvmVariants:
            for debugLevel in _jdkDebugLevels:
                name = jvmVariant + '-' + debugLevel
                eclProjectDir = join(self.dir, 'eclipse', name)
                mx.ensure_dir_exists(eclProjectDir)

                out = mx.XMLDoc()
                out.open('projectDescription')
                out.element('name', data='hotspot:' + name)
                out.element('comment', data='')
                out.element('projects', data='')
                out.open('buildSpec')
                out.open('buildCommand')
                out.element('name', data='org.eclipse.cdt.managedbuilder.core.ScannerConfigBuilder')
                out.element('triggers', data='full,incremental')
                out.element('arguments', data='')
                out.close('buildCommand')

                out.close('buildSpec')
                out.open('natures')
                out.element('nature', data='org.eclipse.cdt.core.cnature')
                out.element('nature', data='org.eclipse.cdt.core.ccnature')
                out.element('nature', data='org.eclipse.cdt.managedbuilder.core.managedBuildNature')
                out.element('nature', data='org.eclipse.cdt.managedbuilder.core.ScannerConfigNature')
                out.close('natures')

                if roots:
                    out.open('linkedResources')
                    for r in roots:
                        f = join(_suite.dir, r)
                        out.open('link')
                        out.element('name', data=r)
                        out.element('type', data='2' if isdir(f) else '1')
                        out.element('locationURI', data=mx.get_eclipse_project_rel_locationURI(f, eclProjectDir))
                        out.close('link')

                    out.open('link')
                    out.element('name', data='gensrc')
                    out.element('type', data='2')
                    generated = join(_get_hotspot_build_dir(jvmVariant, debugLevel), 'gensrc')
                    out.element('locationURI', data=mx.get_eclipse_project_rel_locationURI(generated, eclProjectDir))
                    out.close('link')

                    out.close('linkedResources')
                out.close('projectDescription')
                projectFile = join(eclProjectDir, '.project')
                mx.update_file(projectFile, out.xml(indent='\t', newl='\n'))
                if files:
                    files.append(projectFile)

                cprojectTemplate = join(self.dir, 'templates', 'eclipse', 'cproject')
                cprojectFile = join(eclProjectDir, '.cproject')
                with open(cprojectTemplate) as f:
                    content = f.read()
                mx.update_file(cprojectFile, content)
                if files:
                    files.append(cprojectFile)

                settingsDir = join(eclProjectDir, ".settings")
                mx.ensure_dir_exists(settingsDir)
                for name, source in self._get_eclipse_settings_sources().iteritems():
                    out = StringIO.StringIO()
                    print >> out, '# GENERATED -- DO NOT EDIT'
                    print >> out, '# Source:', source
                    with open(source) as f:
                        print >> out, f.read()
                    content = out.getvalue()
                    mx.update_file(join(settingsDir, name), content)
                    if files:
                        files.append(join(settingsDir, name))

    def getBuildTask(self, args):
        return JDKBuildTask(self, args, _vm.debugLevel, _vm.jvmVariant)


class JDKBuildTask(mx.NativeBuildTask):
    def __init__(self, project, args, debugLevel, jvmVariant):
        mx.NativeBuildTask.__init__(self, args, project)
        self.jvmVariant = jvmVariant
        self.debugLevel = debugLevel

    def __str__(self):
        return 'Building JDK[{}, {}]'.format(self.debugLevel, self.jvmVariant)

    def build(self):
        if mx.get_opts().use_jdk_image:
            _runmake(['images'])
        else:
            _runmake([])
        self._newestOutput = None

    def clean(self, forBuild=False):
        if forBuild:  # Let make handle incremental builds
            return
        if exists(_get_jdk_build_dir(self.debugLevel)):
            _runmake(['clean'])
        self._newestOutput = None

# Backwards compatibility for mx_jvmci:8 API
def buildvms(args):
    _runmultimake(args)

def run_vm(args, vm=None, nonZeroIsFatal=True, out=None, err=None, cwd=None, timeout=None, debugLevel=None, vmbuild=None):
    """run a Java program by executing the java executable in a JVMCI JDK"""
    jdkTag = mx.get_jdk_option().tag
    if jdkTag and jdkTag != _JVMCI_JDK_TAG:
        mx.abort('The "--jdk" option must have the tag "' + _JVMCI_JDK_TAG + '" when running a command requiring a JVMCI VM')
    jdk = get_jvmci_jdk(debugLevel=debugLevel or _translateLegacyDebugLevel(vmbuild))
    return jdk.run_java(args, nonZeroIsFatal=nonZeroIsFatal, out=out, err=err, cwd=cwd, timeout=timeout)

def _unittest_vm_launcher(vmArgs, mainClass, mainClassArgs):
    run_vm(vmArgs + [mainClass] + mainClassArgs)

mx_unittest.set_vm_launcher('JVMCI VM launcher', _unittest_vm_launcher)

def _jvmci_gate_runner(args, tasks):
    # Build release server VM now so we can run the unit tests
    with Task('BuildHotSpotJVMCIHosted: release', tasks) as t:
        if t: _runmultimake(['--jdk-jvm-variants', 'server', '--jdk-debug-levels', 'release'])

    # Run unit tests in hosted mode
    with VM(jvmVariant='server', debugLevel='release', jvmciMode='hosted'):
        with Task('JVMCI UnitTests: hosted-release', tasks) as t:
            if t: unittest(['--suite', 'jvmci', '--enable-timing', '--verbose', '--fail-fast'])

    # Build the other VM flavors
    with Task('BuildHotSpotJVMCIOthers: fastdebug', tasks) as t:
        if t: _runmultimake(['--jdk-jvm-variants', 'server', '--jdk-debug-levels', 'fastdebug'])

    with Task('CleanAndBuildIdealGraphVisualizer', tasks, disableJacoco=True) as t:
        if t and platform.processor() != 'sparc':
            buildxml = mx._cygpathU2W(join(_suite.dir, 'src', 'share', 'tools', 'IdealGraphVisualizer', 'build.xml'))
            mx.run(['ant', '-f', buildxml, '-q', 'clean', 'build'], env=_igvBuildEnv())

mx_gate.add_gate_runner(_suite, _jvmci_gate_runner)
mx_gate.add_gate_argument('-g', '--only-build-jvmci', action='store_false', dest='buildNonJVMCI', help='only build the JVMCI VM')

def _igvJdk():
    v8u20 = mx.VersionSpec("1.8.0_20")
    v8u40 = mx.VersionSpec("1.8.0_40")
    v8 = mx.VersionSpec("1.8")
    def _igvJdkVersionCheck(version):
        return version >= v8 and (version < v8u20 or version >= v8u40)
    return mx.get_jdk(_igvJdkVersionCheck, versionDescription='>= 1.8 and < 1.8.0u20 or >= 1.8.0u40', purpose="building & running IGV").home

def _igvBuildEnv():
        # When the http_proxy environment variable is set, convert it to the proxy settings that ant needs
    env = dict(os.environ)
    proxy = os.environ.get('http_proxy')
    if not (proxy is None) and len(proxy) > 0:
        if '://' in proxy:
            # Remove the http:// prefix (or any other protocol prefix)
            proxy = proxy.split('://', 1)[1]
        # Separate proxy server name and port number
        proxyName, proxyPort = proxy.split(':', 1)
        proxyEnv = '-DproxyHost="' + proxyName + '" -DproxyPort=' + proxyPort
        env['ANT_OPTS'] = proxyEnv

    env['JAVA_HOME'] = _igvJdk()
    return env

def igv(args):
    """run the Ideal Graph Visualizer"""
    logFile = '.ideal_graph_visualizer.log'
    with open(join(_suite.dir, logFile), 'w') as fp:
        mx.logv('[Ideal Graph Visualizer log is in ' + fp.name + ']')
        nbplatform = join(_suite.dir, 'src', 'share', 'tools', 'IdealGraphVisualizer', 'nbplatform')

        # Remove NetBeans platform if it is earlier than the current supported version
        if exists(nbplatform):
            updateTrackingFile = join(nbplatform, 'platform', 'update_tracking', 'org-netbeans-core.xml')
            if not exists(updateTrackingFile):
                mx.log('Could not find \'' + updateTrackingFile + '\', removing NetBeans platform')
                shutil.rmtree(nbplatform)
            else:
                dom = xml.dom.minidom.parse(updateTrackingFile)
                currentVersion = mx.VersionSpec(dom.getElementsByTagName('module_version')[0].getAttribute('specification_version'))
                supportedVersion = mx.VersionSpec('3.43.1')
                if currentVersion < supportedVersion:
                    mx.log('Replacing NetBeans platform version ' + str(currentVersion) + ' with version ' + str(supportedVersion))
                    shutil.rmtree(nbplatform)
                elif supportedVersion < currentVersion:
                    mx.log('Supported NetBeans version in igv command should be updated to ' + str(currentVersion))

        if not exists(nbplatform):
            mx.logv('[This execution may take a while as the NetBeans platform needs to be downloaded]')

        env = _igvBuildEnv()
        # make the jar for Batik 1.7 available.
        env['IGV_BATIK_JAR'] = mx.library('BATIK').get_path(True)
        if mx.run(['ant', '-f', mx._cygpathU2W(join(_suite.dir, 'src', 'share', 'tools', 'IdealGraphVisualizer', 'build.xml')), '-l', mx._cygpathU2W(fp.name), 'run'], env=env, nonZeroIsFatal=False):
            mx.abort("IGV ant build & launch failed. Check '" + logFile + "'. You can also try to delete 'src/share/tools/IdealGraphVisualizer/nbplatform'.")

def c1visualizer(args):
    """run the Cl Compiler Visualizer"""
    libpath = join(_suite.dir, 'lib')
    if mx.get_os() == 'windows':
        executable = join(libpath, 'c1visualizer', 'bin', 'c1visualizer.exe')
    else:
        executable = join(libpath, 'c1visualizer', 'bin', 'c1visualizer')

    # Check whether the current C1Visualizer installation is the up-to-date
    if exists(executable) and not exists(mx.library('C1VISUALIZER_DIST').get_path(resolve=False)):
        mx.log('Updating C1Visualizer')
        shutil.rmtree(join(libpath, 'c1visualizer'))

    archive = mx.library('C1VISUALIZER_DIST').get_path(resolve=True)

    if not exists(executable):
        zf = zipfile.ZipFile(archive, 'r')
        zf.extractall(libpath)

    if not exists(executable):
        mx.abort('C1Visualizer binary does not exist: ' + executable)

    if mx.get_os() != 'windows':
        # Make sure that execution is allowed. The zip file does not always specfiy that correctly
        os.chmod(executable, 0777)

    mx.run([executable])

def hsdis(args, copyToDir=None):
    """download the hsdis library

    This is needed to support HotSpot's assembly dumping features.
    By default it downloads the Intel syntax version, use the 'att' argument to install AT&T syntax."""
    flavor = 'intel'
    if 'att' in args:
        flavor = 'att'
    if mx.get_arch() == "sparcv9":
        flavor = "sparcv9"
    lib = mx.add_lib_suffix('hsdis-' + mx.get_arch())
    path = join(_suite.dir, 'lib', lib)

    sha1s = {
        'att/hsdis-amd64.dll' : 'bcbd535a9568b5075ab41e96205e26a2bac64f72',
        'att/hsdis-amd64.so' : '58919ba085d4ef7a513f25bae75e7e54ee73c049',
        'intel/hsdis-amd64.dll' : '6a388372cdd5fe905c1a26ced614334e405d1f30',
        'intel/hsdis-amd64.so' : '844ed9ffed64fe9599638f29a8450c50140e3192',
        'intel/hsdis-amd64.dylib' : 'fdb13ef0d7d23d93dacaae9c98837bea0d4fc5a2',
        'sparcv9/hsdis-sparcv9.so': '970640a9af0bd63641f9063c11275b371a59ee60',
    }

    flavoredLib = flavor + "/" + lib
    if flavoredLib not in sha1s:
        mx.logv("hsdis not supported on this plattform or architecture")
        return

    if not exists(path):
        sha1 = sha1s[flavoredLib]
        sha1path = path + '.sha1'
        mx.download_file_with_sha1('hsdis', path, ['https://lafo.ssw.uni-linz.ac.at/pub/hsdis/' + flavoredLib], sha1, sha1path, True, True, sources=False)
    if copyToDir is not None and exists(copyToDir):
        shutil.copy(path, copyToDir)

def hcfdis(args):
    """disassemble HexCodeFiles embedded in text files

    Run a tool over the input files to convert all embedded HexCodeFiles
    to a disassembled format."""

    parser = ArgumentParser(prog='mx hcfdis')
    parser.add_argument('-m', '--map', help='address to symbol map applied to disassembler output')
    parser.add_argument('files', nargs=REMAINDER, metavar='files...')

    args = parser.parse_args(args)

    path = mx.library('HCFDIS').get_path(resolve=True)
    mx.run_java(['-cp', path, 'com.oracle.max.hcfdis.HexCodeFileDis'] + args.files)

    if args.map is not None:
        addressRE = re.compile(r'0[xX]([A-Fa-f0-9]+)')
        with open(args.map) as fp:
            lines = fp.read().splitlines()
        symbols = dict()
        for l in lines:
            addressAndSymbol = l.split(' ', 1)
            if len(addressAndSymbol) == 2:
                address, symbol = addressAndSymbol
                if address.startswith('0x'):
                    address = long(address, 16)
                    symbols[address] = symbol
        for f in args.files:
            with open(f) as fp:
                lines = fp.read().splitlines()
            updated = False
            for i in range(0, len(lines)):
                l = lines[i]
                for m in addressRE.finditer(l):
                    sval = m.group(0)
                    val = long(sval, 16)
                    sym = symbols.get(val)
                    if sym:
                        l = l.replace(sval, sym)
                        updated = True
                        lines[i] = l
            if updated:
                mx.log('updating ' + f)
                with open('new_' + f, "w") as fp:
                    for l in lines:
                        print >> fp, l

def jol(args):
    """Java Object Layout"""
    joljar = mx.library('JOL_INTERNALS').get_path(resolve=True)
    candidates = mx.findclass(args, logToConsole=False, matcher=lambda s, classname: s == classname or classname.endswith('.' + s) or classname.endswith('$' + s))

    if len(candidates) > 0:
        candidates = mx.select_items(sorted(candidates))
    else:
        # mx.findclass can be mistaken, don't give up yet
        candidates = args

    run_vm(['-javaagent:' + joljar, '-cp', os.pathsep.join([mx.classpath(), joljar]), "org.openjdk.jol.MainObjectInternals"] + candidates)

def _get_openjdk_os():
    # See: common/autoconf/platform.m4
    os = mx.get_os()
    if 'darwin' in os:
        os = 'macosx'
    elif 'linux' in os:
        os = 'linux'
    elif 'solaris' in os:
        os = 'solaris'
    elif 'cygwin' in os or 'mingw' in os:
        os = 'windows'
    return os

def _get_openjdk_cpu():
    cpu = mx.get_arch()
    if cpu == 'amd64':
        cpu = 'x86_64'
    elif cpu == 'sparcv9':
        cpu = 'sparcv9'
    return cpu

def _get_openjdk_os_cpu():
    return _get_openjdk_os() + '-' + _get_openjdk_cpu()

def _get_jdk_dir():
    suiteParentDir = dirname(_suite.dir)
    # suitParentDir is now something like: /some_prefix/jdk10-hs/open/src
    pathComponents = suiteParentDir.split(os.sep)
    for i in range(0, len(pathComponents)):
        if pathComponents[i] in ["open", "src"]:
            del pathComponents[i:]
            break
    return os.path.join(os.sep, *pathComponents)

def _get_jdk_build_dir(debugLevel=None):
    """
    Gets the directory into which the JDK is built. This directory contains
    the exploded JDK under jdk/ and the JDK image under images/jdk/.
    """
    if debugLevel is None:
        debugLevel = _vm.debugLevel
    name = '{}-{}-{}-{}'.format(_get_openjdk_os_cpu(), 'normal', _vm.jvmVariant, debugLevel)
    return join(_get_jdk_dir(), 'build', name)

_jvmci_bootclasspath_prepends = []

def _get_hotspot_build_dir(jvmVariant=None, debugLevel=None):
    """
    Gets the directory in which a particular HotSpot configuration is built
    (e.g., <JDK_REPO_ROOT>/build/macosx-x86_64-normal-server-release/hotspot/variant-<variant>)
    """
    if jvmVariant is None:
        jvmVariant = _vm.jvmVariant

    name = 'variant-{}'.format(jvmVariant)
    return join(_get_jdk_build_dir(debugLevel=debugLevel), 'hotspot', name)

class JVMCI9JDKConfig(mx.JDKConfig):
    def __init__(self, debugLevel):
        self.debugLevel = debugLevel
        jdkBuildDir = _get_jdk_build_dir(debugLevel)
        jdkDir = join(jdkBuildDir, 'images', 'jdk') if mx.get_opts().use_jdk_image else join(jdkBuildDir, 'jdk')
        mx.JDKConfig.__init__(self, jdkDir, tag=_JVMCI_JDK_TAG)

    def parseVmArgs(self, args, addDefaultArgs=True):
        args = mx.expand_project_in_args(args, insitu=False)
        jacocoArgs = mx_gate.get_jacoco_agent_args()
        if jacocoArgs:
            args = jacocoArgs + args

        args = ['-Xbootclasspath/p:' + dep.classpath_repr() for dep in _jvmci_bootclasspath_prepends] + args

        # Remove JVMCI jars from class path. They are only necessary when
        # compiling with a javac from JDK8 or earlier.
        cpIndex, cp = mx.find_classpath_arg(args)
        if cp:
            excluded = frozenset([dist.path for dist in _suite.dists])
            cp = os.pathsep.join([e for e in cp.split(os.pathsep) if e not in excluded])
            args[cpIndex] = cp

        if '-version' in args:
            ignoredArgs = args[args.index('-version') + 1:]
            if  len(ignoredArgs) > 0:
                mx.log("Warning: The following options will be ignored by the vm because they come after the '-version' argument: " + ' '.join(ignoredArgs))
        return self.processArgs(args, addDefaultArgs=addDefaultArgs)

    # Overrides JDKConfig
    def run_java(self, args, vm=None, nonZeroIsFatal=True, out=None, err=None, cwd=None, timeout=None, env=None, addDefaultArgs=True):
        if vm is None:
            vm = 'server'

        args = self.parseVmArgs(args, addDefaultArgs=addDefaultArgs)

        jvmciModeArgs = _jvmciModes[_vm.jvmciMode]
        cmd = [self.java] + ['-' + vm] + jvmciModeArgs + args
        return mx.run(cmd, nonZeroIsFatal=nonZeroIsFatal, out=out, err=err, cwd=cwd)

"""
The dict of JVMCI JDKs indexed by debug-level names.
"""
_jvmci_jdks = {}

def get_jvmci_jdk(debugLevel=None):
    """
    Gets the JVMCI JDK corresponding to 'debugLevel'.
    """
    if not debugLevel:
        debugLevel = _vm.debugLevel
    jdk = _jvmci_jdks.get(debugLevel)
    if jdk is None:
        try:
            jdk = JVMCI9JDKConfig(debugLevel)
        except mx.JDKConfigException as e:
            jdkBuildDir = _get_jdk_build_dir(debugLevel)
            msg = 'Error with the JDK built into {}:\n{}\nTry (re)building it with: mx --jdk-debug-level={} make'
            if mx.get_opts().use_jdk_image:
                msg += ' images'
            mx.abort(msg.format(jdkBuildDir, e.message, debugLevel))
        _jvmci_jdks[debugLevel] = jdk
    return jdk

class JVMCI9JDKFactory(mx.JDKFactory):
    def getJDKConfig(self):
        jdk = get_jvmci_jdk(_vm.debugLevel)
        return jdk

    def description(self):
        return "JVMCI JDK"

mx.update_commands(_suite, {
    'make': [_runmake, '[args...]', _makehelp],
    'multimake': [_runmultimake, '[options]'],
    'c1visualizer' : [c1visualizer, ''],
    'hsdis': [hsdis, '[att]'],
    'hcfdis': [hcfdis, ''],
    'igv' : [igv, ''],
    'jol' : [jol, ''],
    'vm': [run_vm, '[-options] class [args...]'],
})

mx.add_argument('-M', '--jvmci-mode', action='store', choices=sorted(_jvmciModes.viewkeys()), help='the JVM variant type to build/run (default: ' + _vm.jvmciMode + ')')
mx.add_argument('--jdk-jvm-variant', '--vm', action='store', choices=_jdkJvmVariants + sorted(_legacyVms.viewkeys()), help='the JVM variant type to build/run (default: ' + _vm.jvmVariant + ')')
mx.add_argument('--jdk-debug-level', '--vmbuild', action='store', choices=_jdkDebugLevels + sorted(_legacyVmbuilds.viewkeys()), help='the JDK debug level to build/run (default: ' + _vm.debugLevel + ')')
mx.add_argument('-I', '--use-jdk-image', action='store_true', help='build/run JDK image instead of exploded JDK')

mx.addJDKFactory(_JVMCI_JDK_TAG, mx.JavaCompliance('9'), JVMCI9JDKFactory())

def mx_post_parse_cmd_line(opts):
    mx.set_java_command_default_jdk_tag(_JVMCI_JDK_TAG)

    jdkTag = mx.get_jdk_option().tag

    jvmVariant = None
    debugLevel = None
    jvmciMode = None

    if opts.jdk_jvm_variant is not None:
        jvmVariant = opts.jdk_jvm_variant
        if jdkTag and jdkTag != _JVMCI_JDK_TAG:
            mx.warn('Ignoring "--jdk-jvm-variant" option as "--jdk" tag is not "' + _JVMCI_JDK_TAG + '"')

    if opts.jdk_debug_level is not None:
        debugLevel = _translateLegacyDebugLevel(opts.jdk_debug_level)
        if jdkTag and jdkTag != _JVMCI_JDK_TAG:
            mx.warn('Ignoring "--jdk-debug-level" option as "--jdk" tag is not "' + _JVMCI_JDK_TAG + '"')

    if opts.jvmci_mode is not None:
        jvmciMode = opts.jvmci_mode
        if jdkTag and jdkTag != _JVMCI_JDK_TAG:
            mx.warn('Ignoring "--jvmci-mode" option as "--jdk" tag is not "' + _JVMCI_JDK_TAG + '"')

    _vm.update(jvmVariant, debugLevel, jvmciMode)
