http://scons.tigris.org/issues/show_bug.cgi?id=2345
[scons.git] / src / engine / SCons / Tool / intelc.py
index f979700f82f9526ead020ad963d3ea8def848ec6..fffda70bf51cb97ccf8063bbcc41c3813862bc4d 100644 (file)
@@ -34,96 +34,246 @@ selection method.
 
 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
 
-import sys, os.path, glob, re
+import math, sys, os.path, glob, string, re
 
-is_win32 = sys.platform == 'win32'
+is_windows = sys.platform == 'win32'
+is_win64 = is_windows and (os.environ['PROCESSOR_ARCHITECTURE'] == 'AMD64' or 
+                           ('PROCESSOR_ARCHITEW6432' in os.environ and
+                            os.environ['PROCESSOR_ARCHITEW6432'] == 'AMD64'))
 is_linux = sys.platform == 'linux2'
+is_mac     = sys.platform == 'darwin'
 
-if is_win32:
+if is_windows:
     import SCons.Tool.msvc
 elif is_linux:
     import SCons.Tool.gcc
+elif is_mac:
+    import SCons.Tool.gcc
 import SCons.Util
 import SCons.Warnings
 
-
-def fltcmp(a, b):
-    """Compare strings as floats"""
-    return cmp(float(b), float(a))
+# Exceptions for this tool
+class IntelCError(SCons.Errors.InternalError):
+    pass
+class MissingRegistryError(IntelCError): # missing registry entry
+    pass
+class MissingDirError(IntelCError):     # dir not found
+    pass
+class NoRegistryModuleError(IntelCError): # can't read registry at all
+    pass
+
+def uniquify(s):
+    """Return a sequence containing only one copy of each unique element from input sequence s.
+    Does not preserve order.
+    Input sequence must be hashable (i.e. must be usable as a dictionary key)."""
+    u = {}
+    for x in s:
+        u[x] = 1
+    return u.keys()
+
+def linux_ver_normalize(vstr):
+    """Normalize a Linux compiler version number.
+    Intel changed from "80" to "9.0" in 2005, so we assume if the number
+    is greater than 60 it's an old-style number and otherwise new-style.
+    Always returns an old-style float like 80 or 90 for compatibility with Windows.
+    Shades of Y2K!"""
+    # Check for version number like 9.1.026: return 91.026
+    m = re.match(r'([0-9]+)\.([0-9]+)\.([0-9]+)', vstr)
+    if m:
+        vmaj,vmin,build = m.groups()
+        return float(vmaj) * 10 + float(vmin) + float(build) / 1000.;
+    else:
+        f = float(vstr)
+        if is_windows:
+            return f
+        else:
+            if f < 60: return f * 10.0
+            else: return f
+
+def check_abi(abi):
+    """Check for valid ABI (application binary interface) name,
+    and map into canonical one"""
+    if not abi:
+        return None
+    abi = abi.lower()
+    # valid_abis maps input name to canonical name
+    if is_windows:
+        valid_abis = {'ia32'  : 'ia32',
+                      'x86'   : 'ia32',
+                      'ia64'  : 'ia64',
+                      'em64t' : 'em64t',
+                      'amd64' : 'em64t'}
+    if is_linux:
+        valid_abis = {'ia32'   : 'ia32',
+                      'x86'    : 'ia32',
+                      'x86_64' : 'x86_64',
+                      'em64t'  : 'x86_64',
+                      'amd64'  : 'x86_64'}
+    if is_mac:
+        valid_abis = {'ia32'   : 'ia32',
+                      'x86'    : 'ia32',
+                      'x86_64' : 'x86_64',
+                      'em64t'  : 'x86_64'}
+    try:
+        abi = valid_abis[abi]
+    except KeyError:
+        raise SCons.Errors.UserError("Intel compiler: Invalid ABI %s, valid values are %s"% \
+              (abi, valid_abis.keys()))
+    return abi
+
+def vercmp(a, b):
+    """Compare strings as floats,
+    but Intel changed Linux naming convention at 9.0"""
+    return cmp(linux_ver_normalize(b), linux_ver_normalize(a))
+
+def get_version_from_list(v, vlist):
+    """See if we can match v (string) in vlist (list of strings)
+    Linux has to match in a fuzzy way."""
+    if is_windows:
+        # Simple case, just find it in the list
+        if v in vlist: return v
+        else: return None
+    else:
+        # Fuzzy match: normalize version number first, but still return
+        # original non-normalized form.
+        fuzz = 0.001
+        for vi in vlist:
+            if math.fabs(linux_ver_normalize(vi) - linux_ver_normalize(v)) < fuzz:
+                return vi
+        # Not found
+        return None
 
 def get_intel_registry_value(valuename, version=None, abi=None):
     """
-    Return a value from the Intel compiler registry tree. (Win32 only)
+    Return a value from the Intel compiler registry tree. (Windows only)
     """
-
     # Open the key:
-    K = 'Software\\Intel\\Compilers\\C++\\' + version + '\\'+abi.upper()
+    if is_win64:
+        K = 'Software\\Wow6432Node\\Intel\\Compilers\\C++\\' + version + '\\'+abi.upper()
+    else:
+        K = 'Software\\Intel\\Compilers\\C++\\' + version + '\\'+abi.upper()
     try:
         k = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE, K)
     except SCons.Util.RegError:
-        raise SCons.Errors.InternalError, \
-              "%s was not found in the registry, for Intel compiler version %s"%(K, version)
+        raise MissingRegistryError("%s was not found in the registry, for Intel compiler version %s, abi='%s'"%(K, version,abi))
 
     # Get the value:
     try:
         v = SCons.Util.RegQueryValueEx(k, valuename)[0]
         return v  # or v.encode('iso-8859-1', 'replace') to remove unicode?
     except SCons.Util.RegError:
-        raise SCons.Errors.InternalError, \
-              "%s\\%s was not found in the registry."%(K, value)
+        raise MissingRegistryError("%s\\%s was not found in the registry."%(K, valuename))
 
 
 def get_all_compiler_versions():
-    """Returns a sorted list of strings, like "70" or "80"
+    """Returns a sorted list of strings, like "70" or "80" or "9.0"
     with most recent compiler version first.
     """
     versions=[]
-    if is_win32:
-        keyname = 'Software\\Intel\\Compilers\\C++'
-        k = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE,
-                                    keyname)
+    if is_windows:
+        if is_win64:
+            keyname = 'Software\\WoW6432Node\\Intel\\Compilers\\C++'
+        else:
+            keyname = 'Software\\Intel\\Compilers\\C++'
+        try:
+            k = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE,
+                                        keyname)
+        except WindowsError:
+            return []
         i = 0
         versions = []
         try:
             while i < 100:
                 subkey = SCons.Util.RegEnumKey(k, i) # raises EnvironmentError
-                versions.append(subkey)
+                # Check that this refers to an existing dir.
+                # This is not 100% perfect but should catch common
+                # installation issues like when the compiler was installed
+                # and then the install directory deleted or moved (rather
+                # than uninstalling properly), so the registry values
+                # are still there.
+                ok = False
+                for try_abi in ('IA32', 'IA32e',  'IA64', 'EM64T'):
+                    try:
+                        d = get_intel_registry_value('ProductDir', subkey, try_abi)
+                    except MissingRegistryError:
+                        continue  # not found in reg, keep going
+                    if os.path.exists(d): ok = True
+                if ok:
+                    versions.append(subkey)
+                else:
+                    try:
+                        # Registry points to nonexistent dir.  Ignore this
+                        # version.
+                        value = get_intel_registry_value('ProductDir', subkey, 'IA32')
+                    except MissingRegistryError, e:
+
+                        # Registry key is left dangling (potentially
+                        # after uninstalling).
+
+                        print \
+                            "scons: *** Ignoring the registry key for the Intel compiler version %s.\n" \
+                            "scons: *** It seems that the compiler was uninstalled and that the registry\n" \
+                            "scons: *** was not cleaned up properly.\n" % subkey
+                    else:
+                        print "scons: *** Ignoring "+str(value)
+
                 i = i + 1
         except EnvironmentError:
             # no more subkeys
             pass
     elif is_linux:
-        # Typical dir here is /opt/intel_cc_80.
         for d in glob.glob('/opt/intel_cc_*'):
-            versions.append(re.search(r'cc_(.*)$', d).group(1))
-    versions.sort(fltcmp)
-    return versions
-
-def get_intel_compiler_top(version=None, abi=None):
+            # Typical dir here is /opt/intel_cc_80.
+            m = re.search(r'cc_(.*)$', d)
+            if m:
+                versions.append(m.group(1))
+        for d in glob.glob('/opt/intel/cc*/*'):
+            # Typical dir here is /opt/intel/cc/9.0 for IA32,
+            # /opt/intel/cce/9.0 for EMT64 (AMD64)
+            m = re.search(r'([0-9.]+)$', d)
+            if m:
+                versions.append(m.group(1))
+    elif is_mac:
+        for d in glob.glob('/opt/intel/cc*/*'):
+            # Typical dir here is /opt/intel/cc/9.0 for IA32,
+            # /opt/intel/cce/9.0 for EMT64 (AMD64)
+            m = re.search(r'([0-9.]+)$', d)
+            if m:
+                versions.append(m.group(1))
+    return sorted(uniquify(versions))       # remove dups
+
+def get_intel_compiler_top(version, abi):
     """
     Return the main path to the top-level dir of the Intel compiler,
-    using the given version or latest if None.
+    using the given version.
     The compiler will be in <top>/bin/icl.exe (icc on linux),
     the include dir is <top>/include, etc.
     """
 
-    if is_win32:
+    if is_windows:
         if not SCons.Util.can_read_reg:
-            raise SCons.Errors.InternalError, "No Windows registry module was found"
+            raise NoRegistryModuleError("No Windows registry module was found")
         top = get_intel_registry_value('ProductDir', version, abi)
-
-        if not os.path.exists(os.path.join(top, "Bin", "icl.exe")):
-            raise SCons.Errors.InternalError, \
-                  "Can't find Intel compiler in %s"%(top)
-    elif is_linux:
-        top = '/opt/intel_cc_%s'%version
-        if not os.path.exists(os.path.join(top, "bin", "icc")):
-            raise SCons.Errors.InternalError, \
-                  "Can't find version %s Intel compiler in %s"%(version,top)
+        # pre-11, icl was in Bin.  11 and later, it's in Bin/<abi> apparently.
+        if not os.path.exists(os.path.join(top, "Bin", "icl.exe")) \
+              and not os.path.exists(os.path.join(top, "Bin", abi, "icl.exe")):
+            raise MissingDirError("Can't find Intel compiler in %s"%(top))
+    elif is_mac or is_linux:
+        # first dir is new (>=9.0) style, second is old (8.0) style.
+        dirs=('/opt/intel/cc/%s', '/opt/intel_cc_%s')
+        if abi == 'x86_64':
+            dirs=('/opt/intel/cce/%s',)  # 'e' stands for 'em64t', aka x86_64 aka amd64
+        top=None
+        for d in dirs:
+            if os.path.exists(os.path.join(d%version, "bin", "icc")):
+                top = d%version
+                break
+        if not top:
+            raise MissingDirError("Can't find version %s Intel compiler in %s (abi='%s')"%(version,top, abi))
     return top
 
 
-def generate(env, version=None, abi=None, topdir=None, verbose=1):
+def generate(env, version=None, abi=None, topdir=None, verbose=0):
     """Add Builders and construction variables for Intel C/C++ compiler
     to an Environment.
     args:
@@ -134,40 +284,84 @@ def generate(env, version=None, abi=None, topdir=None, verbose=1):
                         If topdir is used, version and abi are ignored.
       verbose: (int)    if >0, prints compiler version used.
     """
-    if not (is_linux or is_win32):
+    if not (is_mac or is_linux or is_windows):
         # can't handle this platform
         return
 
-    if is_win32:
+    if is_windows:
         SCons.Tool.msvc.generate(env)
     elif is_linux:
         SCons.Tool.gcc.generate(env)
-        
+    elif is_mac:
+        SCons.Tool.gcc.generate(env)
+
     # if version is unspecified, use latest
     vlist = get_all_compiler_versions()
     if not version:
         if vlist:
             version = vlist[0]
     else:
-        if version not in vlist:
-            raise SCons.Errors.UserError, \
-                  "Invalid Intel compiler version %s: "%version + \
-                  "installed versions are %s"%(', '.join(vlist))
-
-    # if abi is unspecified, use ia32 (ia64 is another possibility)
+        # User may have specified '90' but we need to get actual dirname '9.0'.
+        # get_version_from_list does that mapping.
+        v = get_version_from_list(version, vlist)
+        if not v:
+            raise SCons.Errors.UserError("Invalid Intel compiler version %s: "%version + \
+                  "installed versions are %s"%(', '.join(vlist)))
+        version = v
+
+    # if abi is unspecified, use ia32
+    # alternatives are ia64 for Itanium, or amd64 or em64t or x86_64 (all synonyms here)
+    abi = check_abi(abi)
     if abi is None:
-        abi = "ia32"                    # or ia64, I believe
-
-    if topdir is None:
+        if is_mac or is_linux:
+            # Check if we are on 64-bit linux, default to 64 then.
+            uname_m = os.uname()[4]
+            if uname_m == 'x86_64':
+                abi = 'x86_64'
+            else:
+                abi = 'ia32'
+        else:
+            if is_win64:
+                abi = 'em64t'
+            else:
+                abi = 'ia32'
+
+    if version and not topdir:
         try:
             topdir = get_intel_compiler_top(version, abi)
-        except (SCons.Util.RegError, SCons.Errors.InternalError):
+        except (SCons.Util.RegError, IntelCError):
             topdir = None
 
-    if topdir:
+    if not topdir:
+        # Normally this is an error, but it might not be if the compiler is
+        # on $PATH and the user is importing their env.
+        class ICLTopDirWarning(SCons.Warnings.Warning):
+            pass
+        if (is_mac or is_linux) and not env.Detect('icc') or \
+           is_windows and not env.Detect('icl'):
+
+            SCons.Warnings.enableWarningClass(ICLTopDirWarning)
+            SCons.Warnings.warn(ICLTopDirWarning,
+                                "Failed to find Intel compiler for version='%s', abi='%s'"%
+                                (str(version), str(abi)))
+        else:
+            # should be cleaned up to say what this other version is
+            # since in this case we have some other Intel compiler installed
+            SCons.Warnings.enableWarningClass(ICLTopDirWarning)
+            SCons.Warnings.warn(ICLTopDirWarning,
+                                "Can't find Intel compiler top dir for version='%s', abi='%s'"%
+                                    (str(version), str(abi)))
 
+    if topdir:
         if verbose:
-            print "Intel C compiler: using version %s, abi %s, in '%s'"%(version,abi,topdir)
+            print "Intel C compiler: using version %s (%g), abi %s, in '%s'"%\
+                  (repr(version), linux_ver_normalize(version),abi,topdir)
+            if is_linux:
+                # Show the actual compiler version by running the compiler.
+                os.system('%s/bin/icc --version'%topdir)
+            if is_mac:
+                # Show the actual compiler version by running the compiler.
+                os.system('%s/bin/icc --version'%topdir)
 
         env['INTEL_C_COMPILER_TOP'] = topdir
         if is_linux:
@@ -175,32 +369,59 @@ def generate(env, version=None, abi=None, topdir=None, verbose=1):
                    'LIB'             : 'lib',
                    'PATH'            : 'bin',
                    'LD_LIBRARY_PATH' : 'lib'}
-            for p in paths:
+            for p in paths.keys():
                 env.PrependENVPath(p, os.path.join(topdir, paths[p]))
-        if is_win32:
+        if is_mac:
+            paths={'INCLUDE'         : 'include',
+                   'LIB'             : 'lib',
+                   'PATH'            : 'bin',
+                   'LD_LIBRARY_PATH' : 'lib'}
+            for p in paths.keys():
+                env.PrependENVPath(p, os.path.join(topdir, paths[p]))
+        if is_windows:
             #       env key    reg valname   default subdir of top
             paths=(('INCLUDE', 'IncludeDir', 'Include'),
                    ('LIB'    , 'LibDir',     'Lib'),
                    ('PATH'   , 'BinDir',     'Bin'))
+            # We are supposed to ignore version if topdir is set, so set
+            # it to the emptry string if it's not already set.
+            if version is None:
+                version = ''
             # Each path has a registry entry, use that or default to subdir
             for p in paths:
                 try:
                     path=get_intel_registry_value(p[1], version, abi)
-                    env.PrependENVPath(p[0], ';'.split(path))
-                    # print "ICL %s: %s, final=%s"%(p[0], path, str(env['ENV'][p[0]]))
-                except:
+                    # These paths may have $(ICInstallDir)
+                    # which needs to be substituted with the topdir.
+                    path=path.replace('$(ICInstallDir)', topdir + os.sep)
+                except IntelCError:
+                    # Couldn't get it from registry: use default subdir of topdir
                     env.PrependENVPath(p[0], os.path.join(topdir, p[2]))
+                else:
+                    env.PrependENVPath(p[0], path.split(os.pathsep))
+                    # print "ICL %s: %s, final=%s"%(p[0], path, str(env['ENV'][p[0]]))
 
-    if is_win32:
+    if is_windows:
         env['CC']        = 'icl'
         env['CXX']       = 'icl'
         env['LINK']      = 'xilink'
     else:
         env['CC']        = 'icc'
         env['CXX']       = 'icpc'
-        env['LINK']      = '$CC'
-
-    if is_win32:
+        # Don't reset LINK here;
+        # use smart_link which should already be here from link.py.
+        #env['LINK']      = '$CC'
+        env['AR']        = 'xiar'
+        env['LD']        = 'xild' # not used by default
+
+    # This is not the exact (detailed) compiler version,
+    # just the major version as determined above or specified
+    # by the user.  It is a float like 80 or 90, in normalized form for Linux
+    # (i.e. even for Linux 9.0 compiler, still returns 90 rather than 9.0)
+    if version:
+        env['INTEL_C_COMPILER_VERSION']=linux_ver_normalize(version)
+
+    if is_windows:
         # Look for license file dir
         # in system environment, registry, and default location.
         envlicdir = os.environ.get("INTEL_LICENSE_FILE", '')
@@ -214,7 +435,9 @@ def generate(env, version=None, abi=None, topdir=None, verbose=1):
 
         licdir = None
         for ld in [envlicdir, reglicdir]:
-            if ld and os.path.exists(ld):
+            # If the string contains an '@', then assume it's a network
+            # license (port@system) and good by definition.
+            if ld and (ld.find('@') != -1 or os.path.exists(ld)):
                 licdir = ld
                 break
         if not licdir:
@@ -231,20 +454,29 @@ def generate(env, version=None, abi=None, topdir=None, verbose=1):
         env['ENV']['INTEL_LICENSE_FILE'] = licdir
 
 def exists(env):
-    if not (is_linux or is_win32):
+    if not (is_mac or is_linux or is_windows):
         # can't handle this platform
         return 0
 
     try:
-        top = get_intel_compiler_top()
-    except (SCons.Util.RegError, SCons.Errors.InternalError):
-        top = None
-    if not top:
+        versions = get_all_compiler_versions()
+    except (SCons.Util.RegError, IntelCError):
+        versions = None
+    detected = versions is not None and len(versions) > 0
+    if not detected:
         # try env.Detect, maybe that will work
-        if is_win32:
+        if is_windows:
             return env.Detect('icl')
         elif is_linux:
             return env.Detect('icc')
-    return top is not None
+        elif is_mac:
+            return env.Detect('icc')
+    return detected
 
 # end of file
+
+# Local Variables:
+# tab-width:4
+# indent-tabs-mode:nil
+# End:
+# vim: set expandtab tabstop=4 shiftwidth=4: