      assert isinstance(r, CLVar), type(r)
        assert r.data == ['aa', 'bb', ' cc', 'dd'], r.data
        assert str(r) == 'aa bb  cc dd', str(r)

        # make sure inplace adding a string works as well (issue 2399)
        # UserList would convert the string to a list of chars
        f = CLVar(['aa', 'bb'])
        f += 'cc dd'
        assert isinstance(f, CLVar), type(f)
        assert f.data == ['aa', 'bb', 'cc', 'dd'], f.data
        assert str(f) == 'aa bb cc dd', str(f)

        f = CLVar(['aa', 'bb'])
        f += ' cc dd'
        assert isinstance(f, CLVar), type(f)
        assert f.data == ['aa', 'bb', 'cc', 'dd'], f.data
        assert str(f) == 'aa bb cc dd', str(f)


    def test_Selector(self):
        """Test the Selector class"""

        class MyNode:
            def __init__(self, name):
                self.name = name

            def __str__(self):
                return self.name

            def get_suffix(self):
                return os.path.splitext(self.name)[1]

        s = Selector({'a': 'AAA', 'b': 'BBB'})
        assert s['a'] == 'AAA', s['a']
        assert s['b'] == 'BBB', s['b']
        exc_caught = None
        try:
            x = s['c']
        except KeyError:
            exc_caught = 1
        assert exc_caught, "should have caught a KeyError"
        s['c'] = 'CCC'
        assert s['c'] == 'CCC', s['c']

        class DummyEnv(UserDict):
            def subst(self, key):
                if key[0] == '$':
                    return self[key[1:]]
                return key

        env = DummyEnv()

        s = Selector({'.d': 'DDD', '.e': 'EEE'})
        ret = s(env, [])
        assert ret is None, ret
        ret = s(env, [MyNode('foo.d')])
        assert ret == 'DDD', ret
        ret = s(env, [MyNode('bar.e')])
        assert ret == 'EEE', ret
        ret = s(env, [MyNode('bar.x')])
        assert ret is None, ret
        s[None] = 'XXX'
        ret = s(env, [MyNode('bar.x')])
        assert ret == 'XXX', ret

        env = DummyEnv({'FSUFF': '.f', 'GSUFF': '.g'})

        s = Selector({'$FSUFF': 'FFF', '$GSUFF': 'GGG'})
        ret = s(env, [MyNode('foo.f')])
        assert ret == 'FFF', ret
        ret = s(env, [MyNode('bar.g')])
        assert ret == 'GGG', ret

    def test_adjustixes(self):
        """Test the adjustixes() function"""
        r = adjustixes('file', 'pre-', '-suf')
        assert r == 'pre-file-suf', r
        r = adjustixes('pre-file', 'pre-', '-suf')
        assert r == 'pre-file-suf', r
        r = adjustixes('file-suf', 'pre-', '-suf')
        assert r == 'pre-file-suf', r
        r = adjustixes('pre-file-suf', 'pre-', '-suf')
        assert r == 'pre-file-suf', r
        r = adjustixes('pre-file.xxx', 'pre-', '-suf')
        assert r == 'pre-file.xxx', r
        r = adjustixes('dir/file', 'pre-', '-suf')
        assert r == os.path.join('dir', 'pre-file-suf'), r

        # Verify that the odd case when library name is specified as 'lib'
        # doesn't yield lib.so, but yields the expected liblib.so
        r = adjustixes('PREFIX', 'PREFIX', 'SUFFIX')
        assert r == 'PREFIXPREFIXSUFFIX', "Failed handling when filename = PREFIX [r='%s']" % r

    def test_containsAny(self):
        """Test the containsAny() function"""
        assert containsAny('*.py', '*?[]')
        assert not containsAny('file.txt', '*?[]')

    def test_containsAll(self):
        """Test the containsAll() function"""
        assert containsAll('43221', '123')
        assert not containsAll('134', '123')

    def test_containsOnly(self):
        """Test the containsOnly() function"""
        assert containsOnly('.83', '0123456789.')
        assert not containsOnly('43221', '123')

    def test_LogicalLines(self):
        """Test the LogicalLines class"""
        content = """
foo \\
bar \\
baz
foo
bling \\
bling \\ bling
bling
"""
        fobj = io.StringIO(content)
        lines = LogicalLines(fobj).readlines()
        assert lines == [
            '\n',
            'foo bar baz\n',
            'foo\n',
            'bling bling \\ bling\n',
            'bling\n',
        ], lines

    def test_intern(self):
        s1 = silent_intern("spam")
        s3 = silent_intern(42)
        s4 = silent_intern("spam")
        assert id(s1) == id(s4)


class HashTestCase(unittest.TestCase):

    def test_collect(self):
        """Test collecting a list of signatures into a new signature value
        """
        for algorithm, expected in {
            'md5': ('698d51a19d8a121ce581499d7b701668',
                    '8980c988edc2c78cc43ccb718c06efd5',
                    '53fd88c84ff8a285eb6e0a687e55b8c7'),
            'sha1': ('6216f8a75fd5bb3d5f22b6f9958cdede3fc086c2',
                     '42eda1b5dcb3586bccfb1c69f22f923145271d97',
                     '2eb2f7be4e883ebe52034281d818c91e1cf16256'),
            'sha256': ('f6e0a1e2ac41945a9aa7ff8a8aaa0cebc12a3bcc981a929ad5cf810a090e11ae',
                       '25235f0fcab8767b7b5ac6568786fbc4f7d5d83468f0626bf07c3dbeed391a7a',
                       'f8d3d0729bf2427e2e81007588356332e7e8c4133fae4bceb173b93f33411d17'),
        }.items():
            # if the current platform does not support the algorithm we're looking at,
            # skip the test steps for that algorithm, but display a warning to the user
            if algorithm not in ALLOWED_HASH_FORMATS:
                warnings.warn("Missing hash algorithm {} on this platform, cannot test with it".format(algorithm), ResourceWarning)
            else:
                hs = functools.partial(hash_signature, hash_format=algorithm)
                s = list(map(hs, ('111', '222', '333')))

                assert expected[0] == hash_collect(s[0:1], hash_format=algorithm)
                assert expected[1] == hash_collect(s[0:2], hash_format=algorithm)
                assert expected[2] == hash_collect(s, hash_format=algorithm)

    def test_MD5signature(self):
        """Test generating a signature"""
        for algorithm, expected in {
            'md5': ('698d51a19d8a121ce581499d7b701668',
                    'bcbe3365e6ac95ea2c0343a2395834dd'),
            'sha1': ('6216f8a75fd5bb3d5f22b6f9958cdede3fc086c2',
                     '1c6637a8f2e1f75e06ff9984894d6bd16a3a36a9'),
            'sha256': ('f6e0a1e2ac41945a9aa7ff8a8aaa0cebc12a3bcc981a929ad5cf810a090e11ae',
                       '9b871512327c09ce91dd649b3f96a63b7408ef267c8cc5710114e629730cb61f'),
        }.items():
            # if the current platform does not support the algorithm we're looking at,
            # skip the test steps for that algorithm, but display a warning to the user
            if algorithm not in ALLOWED_HASH_FORMATS:
                warnings.warn("Missing hash algorithm {} on this platform, cannot test with it".format(algorithm), ResourceWarning)
            else:
                s = hash_signature('111', hash_format=algorithm)
                assert expected[0] == s, s

                s = hash_signature('222', hash_format=algorithm)
                assert expected[1] == s, s

# this uses mocking out, which is platform specific, however, the FIPS
# behavior this is testing is also platform-specific, and only would be
# visible in hosts running Linux with the fips_mode kernel flag along
# with using OpenSSL.

class FIPSHashTestCase(unittest.TestCase):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

        ###############################
        # algorithm mocks, can check if we called with usedforsecurity=False for python >= 3.9
        self.fake_md5=lambda usedforsecurity=True: (usedforsecurity, 'md5')
        self.fake_sha1=lambda usedforsecurity=True: (usedforsecurity, 'sha1')
        self.fake_sha256=lambda usedforsecurity=True: (usedforsecurity, 'sha256')
        ###############################

        ###############################
        # hashlib mocks
        md5Available = unittest.mock.Mock(md5=self.fake_md5)
        del md5Available.sha1
        del md5Available.sha256
        self.md5Available=md5Available

        md5Default = unittest.mock.Mock(md5=self.fake_md5, sha1=self.fake_sha1)
        del md5Default.sha256
        self.md5Default=md5Default

        sha1Default = unittest.mock.Mock(sha1=self.fake_sha1, sha256=self.fake_sha256)
        del sha1Default.md5
        self.sha1Default=sha1Default

        sha256Default = unittest.mock.Mock(sha256=self.fake_sha256, **{'md5.side_effect': ValueError, 'sha1.side_effect': ValueError})
        self.sha256Default=sha256Default

        all_throw = unittest.mock.Mock(**{'md5.side_effect': ValueError, 'sha1.side_effect': ValueError, 'sha256.side_effect': ValueError})
        self.all_throw=all_throw
        
        no_algorithms = unittest.mock.Mock()
        del no_algorithms.md5
        del no_algorithms.sha1
        del no_algorithms.sha256
        del no_algorithms.nonexist
        self.no_algorithms=no_algorithms
        
        unsupported_algorithm = unittest.mock.Mock(unsupported=self.fake_sha256)
        del unsupported_algorithm.md5
        del unsupported_algorithm.sha1
        del unsupported_algorithm.sha256
        del unsupported_algorithm.unsupported
        self.unsupported_algorithm=unsupported_algorithm
        ###############################

        ###############################
        # system version mocks
        VersionInfo = namedtuple('VersionInfo', 'major minor micro releaselevel serial')
        v3_8 = VersionInfo(3, 8, 199, 'super-beta', 1337)
        v3_9 = VersionInfo(3, 9, 0, 'alpha', 0)
        v4_8 = VersionInfo(4, 8, 0, 'final', 0)

        self.sys_v3_8 = unittest.mock.Mock(version_info=v3_8)
        self.sys_v3_9 = unittest.mock.Mock(version_info=v3_9)
        self.sys_v4_8 = unittest.mock.Mock(version_info=v4_8)
        ###############################

    def test_basic_failover_bad_hashlib_hash_init(self):
        """Tests that if the hashing function is entirely missing from hashlib (hashlib returns None),
        the hash init function returns None"""
        assert _attempt_init_of_python_3_9_hash_object(None) is None

    def test_basic_failover_bad_hashlib_hash_get(self):
        """Tests that if the hashing function is entirely missing from hashlib (hashlib returns None),
        the hash get function returns None"""
        assert _attempt_get_hash_function("nonexist", self.no_algorithms) is None

    def test_usedforsecurity_flag_behavior(self):
        """Test usedforsecurity flag -> should be set to 'True' on older versions of python, and 'False' on Python >= 3.9"""
        for version, expected in {
            self.sys_v3_8: (True, 'md5'),
            self.sys_v3_9: (False, 'md5'),
            self.sys_v4_8: (False, 'md5'),
        }.items():
            assert _attempt_init_of_python_3_9_hash_object(self.fake_md5, version) == expected
    
    def test_automatic_default_to_md5(self):
        """Test automatic default to md5 even if sha1 available"""
        for version, expected in {
            self.sys_v3_8: (True, 'md5'),
            self.sys_v3_9: (False, 'md5'),
            self.sys_v4_8: (False, 'md5'),
        }.items():
            _set_allowed_viable_default_hashes(self.md5Default, version)
            set_hash_format(None, self.md5Default, version)
            assert _get_hash_object(None, self.md5Default, version) == expected

    def test_automatic_default_to_sha256(self):
        """Test automatic default to sha256 if other algorithms available but throw"""
        for version, expected in {
            self.sys_v3_8: (True, 'sha256'),
            self.sys_v3_9: (False, 'sha256'),
            self.sys_v4_8: (False, 'sha256'),
        }.items():
            _set_allowed_viable_default_hashes(self.sha256Default, version)
            set_hash_format(None, self.sha256Default, version)
            assert _get_hash_object(None, self.sha256Default, version) == expected

    def test_automatic_default_to_sha1(self):
        """Test automatic default to sha1 if md5 is missing from hashlib entirely"""
        for version, expected in {
            self.sys_v3_8: (True, 'sha1'),
            self.sys_v3_9: (False, 'sha1'),
            self.sys_v4_8: (False, 'sha1'),
        }.items():
            _set_allowed_viable_default_hashes(self.sha1Default, version)
            set_hash_format(None, self.sha1Default, version)
            assert _get_hash_object(None, self.sha1Default, version) == expected
    
    def test_no_available_algorithms(self):
        """expect exceptions on no available algorithms or when all algorithms throw"""
        self.assertRaises(SCons.Errors.SConsEnvironmentError, _set_allowed_viable_default_hashes, self.no_algorithms)
        self.assertRaises(SCons.Errors.SConsEnvironmentError, _set_allowed_viable_default_hashes, self.all_throw)
        self.assertRaises(SCons.Errors.SConsEnvironmentError, _set_allowed_viable_default_hashes, self.unsupported_algorithm)

    def test_bad_algorithm_set_attempt(self):
        """expect exceptions on user setting an unsupported algorithm selections, either by host or by SCons"""

        # nonexistant hash algorithm, not supported by SCons
        _set_allowed_viable_default_hashes(self.md5Available)
        self.assertRaises(SCons.Errors.UserError, set_hash_format, 'blah blah blah', hashlib_used=self.no_algorithms)
        
        # md5 is default-allowed, but in this case throws when we attempt to use it
        _set_allowed_viable_default_hashes(self.md5Available)
        self.assertRaises(SCons.Errors.UserError, set_hash_format, 'md5', hashlib_used=self.all_throw)

        # user attempts to use an algorithm that isn't supported by their current system but is supported by SCons
        _set_allowed_viable_default_hashes(self.sha1Default)
        self.assertRaises(SCons.Errors.UserError, set_hash_format, 'md5', hashlib_used=self.all_throw)
        
        # user attempts to use an algorithm that is supported by their current system but isn't supported by SCons
        _set_allowed_viable_default_hashes(self.sha1Default)
        self.assertRaises(SCons.Errors.UserError, set_hash_format, 'unsupported', hashlib_used=self.unsupported_algorithm)

    def tearDown(self):
        """Return SCons back to the normal global state for the hashing functions."""
        _set_allowed_viable_default_hashes(hashlib, sys)
        set_hash_format(None)


class NodeListTestCase(unittest.TestCase):
    def test_simple_attributes(self):
        """Test simple attributes of a NodeList class"""

        class TestClass:
            def __init__(self, name, child=None):
                self.child = child
                self.bar = name

        t1 = TestClass('t1', TestClass('t1child'))
        t2 = TestClass('t2', TestClass('t2child'))
        t3 = TestClass('t3')

        nl = NodeList([t1, t2, t3])
        assert nl.bar == ['t1', 't2', 't3'], nl.bar
        assert nl[0:2].child.bar == ['t1child', 't2child'], \
            nl[0:2].child.bar

    def test_callable_attributes(self):
        """Test callable attributes of a NodeList class"""

        class TestClass:
            def __init__(self, name, child=None):
                self.child = child
                self.bar = name

            def foo(self):
                return self.bar + "foo"

            def getself(self):
                return self

        t1 = TestClass('t1', TestClass('t1child'))
        t2 = TestClass('t2', TestClass('t2child'))
        t3 = TestClass('t3')

        nl = NodeList([t1, t2, t3])
        assert nl.foo() == ['t1foo', 't2foo', 't3foo'], nl.foo()
        assert nl.bar == ['t1', 't2', 't3'], nl.bar
        assert nl.getself().bar == ['t1', 't2', 't3'], nl.getself().bar
        assert nl[0:2].child.foo() == ['t1childfoo', 't2childfoo'], \
            nl[0:2].child.foo()
        assert nl[0:2].child.bar == ['t1child', 't2child'], \
            nl[0:2].child.bar

    def test_null(self):
        """Test a null NodeList"""
        nl = NodeList([])
        r = str(nl)
        assert r == '', r
        for node in nl:
            raise Exception("should not enter this loop")


class flattenTestCase(unittest.TestCase):

    def test_scalar(self):
        """Test flattening a scalar"""
        result = flatten('xyz')
        self.assertEqual(result, ['xyz'], result)

    def test_dictionary_values(self):
        """Test flattening the dictionary values"""
        items = {"a": 1, "b": 2, "c": 3}
        result = flatten(items.values())
        self.assertEqual(sorted(result), [1, 2, 3])


class OsEnviron:
    """Used to temporarily mock os.environ"""

    def __init__(self, environ):
        self._environ = environ

    def start(self):
        self._stored = os.environ
        os.environ = self._environ

    def stop(self):
        os.environ = self._stored
        del self._stored

    def __enter__(self):
        self.start()
        return os.environ

    def __exit__(self, *args):
        self.stop()


class get_env_boolTestCase(unittest.TestCase):
    def test_missing(self):
        env = dict()
        var = get_env_bool(env, 'FOO')
        assert var is False, "var should be False, not %s" % repr(var)
        env = {'FOO': '1'}
        var = get_env_bool(env, 'BAR')
        assert var is False, "var should be False, not %s" % repr(var)

    def test_true(self):
        for foo in ['TRUE', 'True', 'true',
                    'YES', 'Yes', 'yes',
                    'Y', 'y',
                    'ON', 'On', 'on',
                    '1', '20', '-1']:
            env = {'FOO': foo}
            var = get_env_bool(env, 'FOO')
            assert var is True, 'var should be True, not %s' % repr(var)

    def test_false(self):
        for foo in ['FALSE', 'False', 'false',
                    'NO', 'No', 'no',
                    'N', 'n',
                    'OFF', 'Off', 'off',
                    '0']:
            env = {'FOO': foo}
            var = get_env_bool(env, 'FOO', True)
            assert var is False, 'var should be True, not %s' % repr(var)

    def test_default(self):
        env = {'FOO': 'other'}
        var = get_env_bool(env, 'FOO', True)
        assert var is True, 'var should be True, not %s' % repr(var)
        var = get_env_bool(env, 'FOO', False)
        assert var is False, 'var should be False, not %s' % repr(var)


class get_os_env_boolTestCase(unittest.TestCase):
    def test_missing(self):
        with OsEnviron(dict()):
            var = get_os_env_bool('FOO')
            assert var is False, "var should be False, not %s" % repr(var)
        with OsEnviron({'FOO': '1'}):
            var = get_os_env_bool('BAR')
            assert var is False, "var should be False, not %s" % repr(var)

    def test_true(self):
        for foo in ['TRUE', 'True', 'true',
                    'YES', 'Yes', 'yes',
                    'Y', 'y',
                    'ON', 'On', 'on',
                    '1', '20', '-1']:
            with OsEnviron({'FOO': foo}):
                var = get_os_env_bool('FOO')
                assert var is True, 'var should be True, not %s' % repr(var)

    def test_false(self):
        for foo in ['FALSE', 'False', 'false',
                    'NO', 'No', 'no',
                    'N', 'n',
                    'OFF', 'Off', 'off',
                    '0']:
            with OsEnviron({'FOO': foo}):
                var = get_os_env_bool('FOO', True)
                assert var is False, 'var should be True, not %s' % repr(var)

    def test_default(self):
        with OsEnviron({'FOO': 'other'}):
            var = get_os_env_bool('FOO', True)
            assert var is True, 'var should be True, not %s' % repr(var)
            var = get_os_env_bool('FOO', False)
            assert var is False, 'var should be False, not %s' % repr(var)


if __name__ == "__main__":
    unittest.main()

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Warnings.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""The SCons warnings framework."""

import sys

import SCons.Errors

class SConsWarning(SCons.Errors.UserError):
    pass

class WarningOnByDefault(SConsWarning):
    pass


# NOTE:  If you add a new warning class, add it to the man page, too!
# Not all warnings are defined here, some are defined in the location of use

class TargetNotBuiltWarning(SConsWarning): # Should go to OnByDefault
    pass

class CacheVersionWarning(WarningOnByDefault):
    pass

class CacheWriteErrorWarning(SConsWarning):
    pass

class CorruptSConsignWarning(WarningOnByDefault):
    pass

class DependencyWarning(SConsWarning):
    pass

class DevelopmentVersionWarning(WarningOnByDefault):
    pass

class DuplicateEnvironmentWarning(WarningOnByDefault):
    pass

class FutureReservedVariableWarning(WarningOnByDefault):
    pass

class LinkWarning(WarningOnByDefault):
    pass

class MisleadingKeywordsWarning(WarningOnByDefault):
    pass

class MissingSConscriptWarning(WarningOnByDefault):
    pass

class NoObjectCountWarning(WarningOnByDefault):
    pass

class NoParallelSupportWarning(WarningOnByDefault):
    pass

class ReservedVariableWarning(WarningOnByDefault):
    pass

class StackSizeWarning(WarningOnByDefault):
    pass

class VisualCMissingWarning(WarningOnByDefault):
    pass

# Used when MSVC_VERSION and MSVS_VERSION do not point to the
# same version (MSVS_VERSION is deprecated)
class VisualVersionMismatch(WarningOnByDefault):
    pass

class VisualStudioMissingWarning(SConsWarning):
    pass

class FortranCxxMixWarning(LinkWarning):
    pass


# Deprecation warnings

class FutureDeprecatedWarning(SConsWarning):
    pass

class DeprecatedWarning(SConsWarning):
    pass

class MandatoryDeprecatedWarning(DeprecatedWarning):
    pass


# Special case; base always stays DeprecatedWarning
class PythonVersionWarning(DeprecatedWarning):
    pass

class DeprecatedSourceCodeWarning(FutureDeprecatedWarning):
    pass

class TaskmasterNeedsExecuteWarning(DeprecatedWarning):
    pass

class DeprecatedOptionsWarning(MandatoryDeprecatedWarning):
    pass

class DeprecatedDebugOptionsWarning(MandatoryDeprecatedWarning):
    pass

class DeprecatedMissingSConscriptWarning(DeprecatedWarning):
    pass

class ToolQtDeprecatedWarning(FutureDeprecatedWarning):
    pass

# The below is a list of 2-tuples.  The first element is a class object.
# The second element is true if that class is enabled, false if it is disabled.
_enabled = []

# If set, raise the warning as an exception
_warningAsException = False

# If not None, a function to call with the warning
_warningOut = None

def suppressWarningClass(clazz):
    """Suppresses all warnings of type clazz or derived from clazz."""
    _enabled.insert(0, (clazz, False))

def enableWarningClass(clazz):
    """Enables all warnings of type clazz or derived from clazz."""
    _enabled.insert(0, (clazz, True))

def warningAsException(flag=True):
    """Set global _warningAsExeption flag.

    Args:
        flag: value to set warnings-as-exceptions to [default: True]

    Returns:
        The previous value.
    """
    global _warningAsException
    old = _warningAsException
    _warningAsException = flag
    return old

def warn(clazz, *args):
    """Issue a warning, accounting for SCons rules.

    Check if warnings for this class are enabled.
    If warnings are treated as exceptions, raise exception.
    Use the global warning-emitter _warningOut, which allows selecting
    different ways of presenting a traceback (see Script/Main.py)
    """
    warning = clazz(args)
    for cls, flag in _enabled:
        if isinstance(warning, cls):
            if flag:
                if _warningAsException:
                    raise warning

                if _warningOut:
                    _warningOut(warning)
            break

def process_warn_strings(arguments):
    """Process requests to enable/disable warnings.

    The requests are strings passed to the --warn option or the
    SetOption('warn') function.

    An argument to this option should be of the form "warning-class"
    or "no-warning-class".  The warning class is munged and has
    the suffix "Warning" added in order to get an actual class name
    from the classes above, which we need to pass to the
    {enable,disable}WarningClass() functions.

    For example, "deprecated" will enable the DeprecatedWarning class.
    "no-dependency" will disable the DependencyWarning class.

    As a special case, --warn=all and --warn=no-all will enable or
    disable (respectively) the base class of all SCons warnings.
    """

    def _classmunge(s):
        """Convert a warning argument to SConsCase.

        The result is CamelCase, except "Scons" is changed to "SCons"
        """
        s = s.replace("-", " ").title().replace(" ", "")
        return s.replace("Scons", "SCons")

    for arg in arguments:
        enable = True
        if arg.startswith("no-"):
            enable = False
            arg = arg[len("no-") :]
        if arg == 'all':
            class_name = "SConsWarning"
        else:
            class_name = _classmunge(arg) + 'Warning'
        try:
            clazz = globals()[class_name]
        except KeyError:
            sys.stderr.write("No warning type: '%s'\n" % arg)
        else:
            if enable:
                enableWarningClass(clazz)
            elif issubclass(clazz, MandatoryDeprecatedWarning):
                fmt = "Can not disable mandataory warning: '%s'\n"
                sys.stderr.write(fmt % arg)
            else:
                suppressWarningClass(clazz)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/WarningsTests.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

import unittest

import SCons.Warnings

class TestOutput:
    def __call__(self, x):
        args = x.args[0]
        if len(args) == 1:
            args = args[0]
        self.out = str(args)

class WarningsTestCase(unittest.TestCase):
    def test_Warning(self):
        """Test warn function."""

        # Reset global state
        SCons.Warnings._enabled = []
        SCons.Warnings._warningAsException = 0
        
        to = TestOutput()
        SCons.Warnings._warningOut=to
        SCons.Warnings.enableWarningClass(SCons.Warnings.SConsWarning)
        SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
                            "Foo")
        assert to.out == "Foo", to.out
        SCons.Warnings.warn(SCons.Warnings.DependencyWarning,
                            "Foo", 1)
        assert to.out == "('Foo', 1)", to.out

    def test_WarningAsExc(self):
        """Test warnings as exceptions."""
        
        # Reset global state
        SCons.Warnings._enabled = []
        SCons.Warnings._warningAsException = 0

        SCons.Warnings.enableWarningClass(SCons.Warnings.SConsWarning)
        old = SCons.Warnings.warningAsException()
        assert old == 0, old
        exc_caught = 0
        try:
            SCons.Warnings.warn(SCons.Warnings.SConsWarning, "Foo")
        except:
            exc_caught = 1
        assert exc_caught == 1

        old = SCons.Warnings.warningAsException(old)
        assert old == 1, old
        exc_caught = 0
        try:
            SCons.Warnings.warn(SCons.Warnings.SConsWarning, "Foo")
        except:
            exc_caught = 1
        assert exc_caught == 0

    def test_Disable(self):
        """Test disabling/enabling warnings."""

        # Reset global state
        SCons.Warnings._enabled = []
        SCons.Warnings._warningAsException = 0

        to = TestOutput()
        SCons.Warnings._warningOut=to
        to.out = None

        # No warnings by default
        SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
                            "Foo")
        assert to.out is None, to.out

        SCons.Warnings.enableWarningClass(SCons.Warnings.SConsWarning)
        SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
                            "Foo")
        assert to.out == "Foo", to.out

        to.out = None
        SCons.Warnings.suppressWarningClass(SCons.Warnings.DeprecatedWarning)
        SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
                            "Foo")
        assert to.out is None, to.out

        SCons.Warnings.warn(SCons.Warnings.MandatoryDeprecatedWarning,
                            "Foo")
        assert to.out is None, to.out

        # Dependency warnings should still be enabled though
        SCons.Warnings.enableWarningClass(SCons.Warnings.SConsWarning)
        SCons.Warnings.warn(SCons.Warnings.DependencyWarning,
                            "Foo")
        assert to.out == "Foo", to.out

        # Try reenabling all warnings...
        SCons.Warnings.enableWarningClass(SCons.Warnings.SConsWarning)

        SCons.Warnings.enableWarningClass(SCons.Warnings.SConsWarning)
        SCons.Warnings.warn(SCons.Warnings.DeprecatedWarning,
                            "Foo")
        assert to.out == "Foo", to.out

if __name__ == "__main__":
    unittest.main()

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/__init__.py

__version__="4.3.0"
__copyright__="Copyright (c) 2001 - 2021 The SCons Foundation"
__developer__="bdeegan"
__date__="Tue, 16 Nov 2021 19:09:21 +0000"
__buildsys__="octodog"
__revision__="559790274f66fa55251f5754de34820a29c7327a"
__build__="559790274f66fa55251f5754de34820a29c7327a"
# make sure compatibility is always in place
import SCons.compat # noqa

# SCons/__main__.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

import SCons.Script
# this does all the work, and calls sys.exit
# with the proper exit status when done.
SCons.Script.main()


# SCons/cpp.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""SCons C Pre-Processor module"""

import os
import re

# First "subsystem" of regular expressions that we set up:
#
# Stuff to turn the C preprocessor directives in a file's contents into
# a list of tuples that we can process easily.
#
# A table of regular expressions that fetch the arguments from the rest of
# a C preprocessor line.  Different directives have different arguments
# that we want to fetch, using the regular expressions to which the lists
# of preprocessor directives map.
cpp_lines_dict = {
    # Fetch the rest of a #if/#elif as one argument,
    # with white space optional.
    ('if', 'elif')      : r'\s*(.+)',

    # Fetch the rest of a #ifdef/#ifndef as one argument,
    # separated from the keyword by white space.
    ('ifdef', 'ifndef',): r'\s+(.+)',

    # Fetch the rest of a #import/#include/#include_next line as one
    # argument, with white space optional.
    ('import', 'include', 'include_next',)
                        : r'\s*(.+)',

    # We don't care what comes after a #else or #endif line.
    ('else', 'endif',)  : '',

    # Fetch three arguments from a #define line:
    #   1) The #defined keyword.
    #   2) The optional parentheses and arguments (if it's a function-like
    #      macro, '' if it's not).
    #   3) The expansion value.
    ('define',)         : r'\s+([_A-Za-z][_A-Za-z0-9_]*)(\([^)]*\))?\s*(.*)',

    # Fetch the #undefed keyword from a #undef line.
    ('undef',)          : r'\s+([_A-Za-z][A-Za-z0-9_]*)',
}

# Create a table that maps each individual C preprocessor directive to
# the corresponding compiled regular expression that fetches the arguments
# we care about.
Table = {}
for op_list, expr in cpp_lines_dict.items():
    e = re.compile(expr)
    for op in op_list:
        Table[op] = e
del e
del op
del op_list

# Create a list of the expressions we'll use to match all of the
# preprocessor directives.  These are the same as the directives
# themselves *except* that we must use a negative lookahead assertion
# when matching "if" so it doesn't match the "if" in "ifdef" or "ifndef".
override = {
    'if'                        : 'if(?!n?def)',
}
l = [override.get(x, x) for x in Table.keys()]


# Turn the list of expressions into one big honkin' regular expression
# that will match all the preprocessor lines at once.  This will return
# a list of tuples, one for each preprocessor line.  The preprocessor
# directive will be the first element in each tuple, and the rest of
# the line will be the second element.
e = r'^\s*#\s*(' + '|'.join(l) + ')(.*)$'

# And last but not least, compile the expression.
CPP_Expression = re.compile(e, re.M)

# A list with RE to cleanup CPP Expressions (tuples)
# We should remove all comments and carriage returns (\r) before evaluating
CPP_Expression_Cleaner_List = [
    r"/\*.*\*/",
    r"/\*.*",
    r"//.*",
    r"\r"
]
CPP_Expression_Cleaner_RE = re.compile(
    r"\s*(" + "|".join(CPP_Expression_Cleaner_List) + ")")

def Cleanup_CPP_Expressions(ts):
    return [(t[0], CPP_Expression_Cleaner_RE.sub("", t[1])) for t in ts]

#
# Second "subsystem" of regular expressions that we set up:
#
# Stuff to translate a C preprocessor expression (as found on a #if or
# #elif line) into an equivalent Python expression that we can eval().
#

# A dictionary that maps the C representation of Boolean operators
# to their Python equivalents.
CPP_to_Python_Ops_Dict = {
    '!'         : ' not ',
    '!='        : ' != ',
    '&&'        : ' and ',
    '||'        : ' or ',
    '?'         : ' and ',
    ':'         : ' or ',
}

CPP_to_Python_Ops_Sub = lambda m: CPP_to_Python_Ops_Dict[m.group(0)]

# We have to sort the keys by length so that longer expressions
# come *before* shorter expressions--in particular, "!=" must
# come before "!" in the alternation.  Without this, the Python
# re module, as late as version 2.2.2, empirically matches the
# "!" in "!=" first, instead of finding the longest match.
# What's up with that?
l = sorted(list(CPP_to_Python_Ops_Dict.keys()), key=lambda a: len(a), reverse=True)

# Turn the list of keys into one regular expression that will allow us
# to substitute all of the operators at once.
expr = '|'.join(map(re.escape, l))

# ...and compile the expression.
CPP_to_Python_Ops_Expression = re.compile(expr)

# A separate list of expressions to be evaluated and substituted
# sequentially, not all at once.
CPP_to_Python_Eval_List = [
    [r'defined\s+(\w+)',                 '"\\1" in __dict__'],
    [r'defined\s*\((\w+)\)',             '"\\1" in __dict__'],
    [r'(0x[0-9A-Fa-f]+)(?:L|UL)?',  '\\1'],
    [r'(\d+)(?:L|UL)?',  '\\1'],
]

# Replace the string representations of the regular expressions in the
# list with compiled versions.
for l in CPP_to_Python_Eval_List:
    l[0] = re.compile(l[0])

# Wrap up all of the above into a handy function.
def CPP_to_Python(s):
    """
    Converts a C pre-processor expression into an equivalent
    Python expression that can be evaluated.
    """
    s = CPP_to_Python_Ops_Expression.sub(CPP_to_Python_Ops_Sub, s)
    for expr, repl in CPP_to_Python_Eval_List:
        s = re.sub(expr, repl, s)
    return s



del expr
del l
del override



class FunctionEvaluator:
    """Handles delayed evaluation of a #define function call."""

    def __init__(self, name, args, expansion):
        """
        Squirrels away the arguments and expansion value of a #define
        macro function for later evaluation when we must actually expand
        a value that uses it.
        """
        self.name = name
        self.args = function_arg_separator.split(args)
        try:
            expansion = expansion.split('##')
        except AttributeError:
            pass
        self.expansion = expansion

    def __call__(self, *values):
        """
        Evaluates the expansion of a #define macro function called
        with the specified values.
        """
        if len(self.args) != len(values):
            raise ValueError("Incorrect number of arguments to `%s'" % self.name)
        # Create a dictionary that maps the macro arguments to the
        # corresponding values in this "call."  We'll use this when we
        # eval() the expansion so that arguments will get expanded to
        # the right values.
        args = self.args
        localvars = {k: v for k, v in zip(args, values)}
        parts = [s if s in args else repr(s) for s in self.expansion]
        statement = ' + '.join(parts)

        return eval(statement, globals(), localvars)


# Find line continuations.
line_continuations = re.compile('\\\\\r?\n')

# Search for a "function call" macro on an expansion.  Returns the
# two-tuple of the "function" name itself, and a string containing the
# arguments within the call parentheses.
function_name = re.compile(r'(\S+)\(([^)]*)\)')

# Split a string containing comma-separated function call arguments into
# the separate arguments.
function_arg_separator = re.compile(r',\s*')



class PreProcessor:
    """The main workhorse class for handling C pre-processing."""

    def __init__(self, current=os.curdir, cpppath=(), dict={}, all=0, depth=-1):
        global Table

        cpppath = tuple(cpppath)

        self.searchpath = {
            '"': (current,) + cpppath,
            '<': cpppath + (current,),
        }

        # Initialize our C preprocessor namespace for tracking the
        # values of #defined keywords.  We use this namespace to look
        # for keywords on #ifdef/#ifndef lines, and to eval() the
        # expressions on #if/#elif lines (after massaging them from C to
        # Python).
        self.cpp_namespace = dict.copy()
        self.cpp_namespace['__dict__'] = self.cpp_namespace

        # Return all includes without resolving
        if all:
            self.do_include = self.all_include

        # Max depth of nested includes:
        # -1 = unlimited
        # 0 - disabled nesting
        # >0 - number of allowed nested includes
        self.depth = depth

        # For efficiency, a dispatch table maps each C preprocessor
        # directive (#if, #define, etc.) to the method that should be
        # called when we see it.  We accomodate state changes (#if,
        # #ifdef, #ifndef) by pushing the current dispatch table on a
        # stack and changing what method gets called for each relevant
        # directive we might see next at this level (#else, #elif).
        # #endif will simply pop the stack.
        d = {'scons_current_file': self.scons_current_file}
        for op in Table.keys():
            d[op] = getattr(self, 'do_' + op)
        self.default_table = d

    def __call__(self, file):
        """
        Pre-processes a file.

        This is the main public entry point.
        """
        self.current_file = file
        return self.process_file(file)

    def process_file(self, file):
        """
        Pre-processes a file.

        This is the main internal entry point.
        """
        return self._process_tuples(self.tupleize(self.read_file(file)), file)

    def process_contents(self, contents):
        """
        Pre-processes a file contents.

        Is used by tests
        """
        return self._process_tuples(self.tupleize(contents))

    def _process_tuples(self, tuples, file=None):
        self.stack = []
        self.dispatch_table = self.default_table.copy()
        self.current_file = file
        self.tuples = tuples

        self.initialize_result(file)
        while self.tuples:
            t = self.tuples.pop(0)
            # Uncomment to see the list of tuples being processed (e.g.,
            # to validate the CPP lines are being translated correctly).
            # print(t)
            self.dispatch_table[t[0]](t)
        return self.finalize_result(file)

    def tupleize(self, contents):
        """
        Turns the contents of a file into a list of easily-processed
        tuples describing the CPP lines in the file.

        The first element of each tuple is the line's preprocessor
        directive (#if, #include, #define, etc., minus the initial '#').
        The remaining elements are specific to the type of directive, as
        pulled apart by the regular expression.
        """
        return self._match_tuples(self._parse_tuples(contents))

    def _parse_tuples(self, contents):
        global CPP_Expression
        contents = line_continuations.sub('', contents)
        tuples = CPP_Expression.findall(contents)
        return Cleanup_CPP_Expressions(tuples)

    def _match_tuples(self, tuples):
        global Table
        result = []
        for t in tuples:
            m = Table[t[0]].match(t[1])
            if m:
                result.append((t[0],) + m.groups())
        return result

    # Dispatch table stack manipulation methods.

    def save(self):
        """
        Pushes the current dispatch table on the stack and re-initializes
        the current dispatch table to the default.
        """
        self.stack.append(self.dispatch_table)
        self.dispatch_table = self.default_table.copy()

    def restore(self):
        """
        Pops the previous dispatch table off the stack and makes it the
        current one.
        """
        try: self.dispatch_table = self.stack.pop()
        except IndexError: pass

    # Utility methods.

    def do_nothing(self, t):
        """
        Null method for when we explicitly want the action for a
        specific preprocessor directive to do nothing.
        """
        pass

    def scons_current_file(self, t):
        self.current_file = t[1]

    def eval_expression(self, t):
        """
        Evaluates a C preprocessor expression.

        This is done by converting it to a Python equivalent and
        eval()ing it in the C preprocessor namespace we use to
        track #define values.
        """
        t = CPP_to_Python(' '.join(t[1:]))
        try:
            return eval(t, self.cpp_namespace)
        except (NameError, TypeError, SyntaxError):
            return 0

    def initialize_result(self, fname):
        self.result = [fname]

    def finalize_result(self, fname):
        return self.result[1:]

    def find_include_file(self, t):
        """
        Finds the #include file for a given preprocessor tuple.
        """
        fname = t[2]
        for d in self.searchpath[t[1]]:
            if d == os.curdir:
                f = fname
            else:
                f = os.path.join(d, fname)
            if os.path.isfile(f):
                return f
        return None

    def read_file(self, file):
        with open(file) as f:
            return f.read()

    # Start and stop processing include lines.

    def start_handling_includes(self, t=None):
        """
        Causes the PreProcessor object to start processing #import,
        #include and #include_next lines.

        This method will be called when a #if, #ifdef, #ifndef or #elif
        evaluates True, or when we reach the #else in a #if, #ifdef,
        #ifndef or #elif block where a condition already evaluated
        False.

        """
        d = self.dispatch_table
        p = self.stack[-1] if self.stack else self.default_table

        for k in ('import', 'include', 'include_next', 'define', 'undef'):
            d[k] = p[k]

    def stop_handling_includes(self, t=None):
        """
        Causes the PreProcessor object to stop processing #import,
        #include and #include_next lines.

        This method will be called when a #if, #ifdef, #ifndef or #elif
        evaluates False, or when we reach the #else in a #if, #ifdef,
        #ifndef or #elif block where a condition already evaluated True.
        """
        d = self.dispatch_table
        d['import'] = self.do_nothing
        d['include'] =  self.do_nothing
        d['include_next'] =  self.do_nothing
        d['define'] =  self.do_nothing
        d['undef'] =  self.do_nothing

    # Default methods for handling all of the preprocessor directives.
    # (Note that what actually gets called for a given directive at any
    # point in time is really controlled by the dispatch_table.)

    def _do_if_else_condition(self, condition):
        """
        Common logic for evaluating the conditions on #if, #ifdef and
        #ifndef lines.
        """
        self.save()
        d = self.dispatch_table
        if condition:
            self.start_handling_includes()
            d['elif'] = self.stop_handling_includes
            d['else'] = self.stop_handling_includes
        else:
            self.stop_handling_includes()
            d['elif'] = self.do_elif
            d['else'] = self.start_handling_includes

    def do_ifdef(self, t):
        """
        Default handling of a #ifdef line.
        """
        self._do_if_else_condition(t[1] in self.cpp_namespace)

    def do_ifndef(self, t):
        """
        Default handling of a #ifndef line.
        """
        self._do_if_else_condition(t[1] not in self.cpp_namespace)

    def do_if(self, t):
        """
        Default handling of a #if line.
        """
        self._do_if_else_condition(self.eval_expression(t))

    def do_elif(self, t):
        """
        Default handling of a #elif line.
        """
        d = self.dispatch_table
        if self.eval_expression(t):
            self.start_handling_includes()
            d['elif'] = self.stop_handling_includes
            d['else'] = self.stop_handling_includes

    def do_else(self, t):
        """
        Default handling of a #else line.
        """
        pass

    def do_endif(self, t):
        """
        Default handling of a #endif line.
        """
        self.restore()

    def do_define(self, t):
        """
        Default handling of a #define line.
        """
        _, name, args, expansion = t
        try:
            expansion = int(expansion)
        except (TypeError, ValueError):
            # handle "defined" chain "! (defined (A) || defined (B)" ...
            if "defined " in expansion:
                self.cpp_namespace[name] = self.eval_expression(t[2:])
                return

        if args:
            evaluator = FunctionEvaluator(name, args[1:-1], expansion)
            self.cpp_namespace[name] = evaluator
        else:
            self.cpp_namespace[name] = expansion

    def do_undef(self, t):
        """
        Default handling of a #undef line.
        """
        try: del self.cpp_namespace[t[1]]
        except KeyError: pass

    def do_import(self, t):
        """
        Default handling of a #import line.
        """
        # XXX finish this -- maybe borrow/share logic from do_include()...?
        pass

    def do_include(self, t):
        """
        Default handling of a #include line.
        """
        t = self.resolve_include(t)
        if not t:
            return
        include_file = self.find_include_file(t)
        # avoid infinite recursion
        if not include_file or include_file in self.result:
            return
        self.result.append(include_file)
        # print include_file, len(self.tuples)

        # Handle maximum depth of nested includes
        if self.depth != -1:
            current_depth = 0
            for t in self.tuples:
                if t[0] == "scons_current_file":
                    current_depth += 1
            if current_depth >= self.depth:
                return

        new_tuples = [('scons_current_file', include_file)] + \
                      self.tupleize(self.read_file(include_file)) + \
                     [('scons_current_file', self.current_file)]
        self.tuples[:] = new_tuples + self.tuples

    # Date: Tue, 22 Nov 2005 20:26:09 -0500
    # From: Stefan Seefeld <seefeld@sympatico.ca>
    #
    # By the way, #include_next is not the same as #include. The difference
    # being that #include_next starts its search in the path following the
    # path that let to the including file. In other words, if your system
    # include paths are ['/foo', '/bar'], and you are looking at a header
    # '/foo/baz.h', it might issue an '#include_next <baz.h>' which would
    # correctly resolve to '/bar/baz.h' (if that exists), but *not* see
    # '/foo/baz.h' again. See http://www.delorie.com/gnu/docs/gcc/cpp_11.html
    # for more reasoning.
    #
    # I have no idea in what context 'import' might be used.

    # XXX is #include_next really the same as #include ?
    do_include_next = do_include

    # Utility methods for handling resolution of include files.

    def resolve_include(self, t):
        """Resolve a tuple-ized #include line.

        This handles recursive expansion of values without "" or <>
        surrounding the name until an initial " or < is found, to handle
        #include FILE where FILE is a #define somewhere else.
        """
        s = t[1].strip()
        while not s[0] in '<"':
            try:
                s = self.cpp_namespace[s]
            except KeyError:
                m = function_name.search(s)

                # Date: Mon, 28 Nov 2016 17:47:13 UTC
                # From: Ivan Kravets <ikravets@platformio.org>
                #
                # Ignore `#include` directive that depends on dynamic macro
                # which is not located in state TABLE
                # For example, `#include MYCONFIG_FILE`
                if not m:
                    return None

                s = self.cpp_namespace[m.group(1)]
                if callable(s):
                    args = function_arg_separator.split(m.group(2))
                    s = s(*args)
            if not s:
                return None
        return (t[0], s[0], s[1:-1])

    def all_include(self, t):
        """
        """
        self.result.append(self.resolve_include(t))


class DumbPreProcessor(PreProcessor):
    """A preprocessor that ignores all #if/#elif/#else/#endif directives
    and just reports back *all* of the #include files (like the classic
    SCons scanner did).

    This is functionally equivalent to using a regular expression to
    find all of the #include lines, only slower.  It exists mainly as
    an example of how the main PreProcessor class can be sub-classed
    to tailor its behavior.
    """
    def __init__(self, *args, **kw):
        PreProcessor.__init__(self, *args, **kw)
        d = self.default_table
        for func in ['if', 'elif', 'else', 'endif', 'ifdef', 'ifndef']:
            d[func] = d[func] = self.do_nothing

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/cppTests.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

import atexit
import unittest

import TestUnit

import cpp


basic_input = """
#include "file1-yes"
#include <file2-yes>
"""


substitution_input = """
#define FILE3   "file3-yes"
#define FILE4   <file4-yes>

#include FILE3
#include FILE4

#define XXX_FILE5       YYY_FILE5
#define YYY_FILE5       ZZZ_FILE5
#define ZZZ_FILE5       FILE5

#define FILE5   "file5-yes"
#define FILE6   <file6-yes>

#define XXX_FILE6       YYY_FILE6
#define YYY_FILE6       ZZZ_FILE6
#define ZZZ_FILE6       FILE6

#include XXX_FILE5
#include XXX_FILE6
"""


ifdef_input = """
#define DEFINED 0

#ifdef	DEFINED /* multi-line comment */
#include "file7-yes"
#else
#include "file7-no"
#endif

#ifdef	NOT_DEFINED
#include <file8-no>
#else
#include <file8-yes>
#endif
"""


if_boolean_input = """
#define ZERO	0  // single-line comment
#define ONE	1

#if ZERO
#include "file9-no"
#else
#include "file9-yes"
#endif

#if ONE
#include <file10-yes>
#else
#include <file10-no>
#endif

#if ZERO
#include "file11-no-1"
#elif ZERO
#include "file11-no-2"
#else
#include "file11-yes"
#endif

#if ZERO
#include <file12-no-1>
#elif ONE
#include <file12-yes>
#else
#include <file12-no-2>
#endif

#if ONE
#include "file13-yes"
#elif ZERO
#include "file13-no-1"
#else
#include "file13-no-2"
#endif

#if ONE
#include <file14-yes>
#elif ONE
#include <file14-no-1>
#else
#include <file14-no-2>
#endif
"""


if_defined_input = """
#define DEFINED_A 0
#define DEFINED_B 0

#if	defined(DEFINED_A)
#include "file15-yes"
#endif

#if	! defined(DEFINED_A)
#include <file16-no>
#else
#include <file16-yes>
#endif

#if	defined DEFINED_A
#include "file17-yes"
#endif

#if	! defined DEFINED_A
#include <file18-no>
#else
#include <file18-yes>
#endif

#if ! (defined (DEFINED_A) || defined (DEFINED_B)
#include <file19-no>
#else
#include <file19-yes>
#endif

"""


expression_input = """
#define ZERO	0
#define ONE	1

#if	ZERO && ZERO
#include "file19-no"
#else
#include "file19-yes"
#endif

#if	ZERO && ONE
#include <file20-no>
#else
#include <file20-yes>
#endif

#if	ONE && ZERO
#include "file21-no"
#else
#include "file21-yes"
#endif

#if	ONE && ONE
#include <file22-yes>
#else
#include <file22-no>
#endif

#if	ZERO || ZERO
#include "file23-no"
#else
#include "file23-yes"
#endif

#if	ZERO || ONE
#include <file24-yes>
#else
#include <file24-no>
#endif

#if	ONE || ZERO
#include "file25-yes"
#else
#include "file25-no"
#endif

#if	ONE || ONE
#include <file26-yes>
#else
#include <file26-no>
#endif

#if	ONE == ONE
#include "file27-yes"
#else
#include "file27-no"
#endif

#if	ONE != ONE
#include <file28-no>
#else
#include <file28-yes>
#endif

#if	! (ONE == ONE)
#include "file29-no"
#else
#include "file29-yes"
#endif

#if ! (ONE != ONE)
#include <file30-yes>
#else
#include <file30-no>
#endif

#if	123456789UL || 0x13L
#include <file301-yes>
#else
#include <file301-no>
#endif
"""


undef_input = """
#define	UNDEFINE	0

#ifdef	UNDEFINE
#include "file31-yes"
#else
#include "file31-no"
#endif

#undef	UNDEFINE

#ifdef	UNDEFINE
#include <file32-no>
#else
#include <file32-yes>
#endif
"""


macro_function_input = """
#define ZERO	0
#define ONE	1

#define	FUNC33(x)	"file33-yes"
#define	FUNC34(x)	<file34-yes>

#include FUNC33(ZERO)
#include FUNC34(ZERO)

#define FILE35		"file35-yes"
#define FILE36		<file36-yes>

#define	FUNC35(x, y)	FILE35
#define	FUNC36(x, y)	FILE36

#include FUNC35(ZERO, ONE)
#include FUNC36(ZERO, ONE)

#define FILE37		"file37-yes"
#define FILE38		<file38-yes>

#define	FUNC37a(x, y)	FILE37
#define	FUNC38a(x, y)	FILE38

#define	FUNC37b(x, y)	FUNC37a(x, y)
#define	FUNC38b(x, y)	FUNC38a(x, y)

#define	FUNC37c(x, y)	FUNC37b(x, y)
#define	FUNC38c(x, y)	FUNC38b(x, y)

#include FUNC37c(ZERO, ONE)
#include FUNC38c(ZERO, ONE)

#define FILE39		"file39-yes"
#define FILE40		<file40-yes>

#define	FUNC39a(x0, y0)	FILE39
#define	FUNC40a(x0, y0)	FILE40

#define	FUNC39b(x1, y2)	FUNC39a(x1, y1)
#define	FUNC40b(x1, y2)	FUNC40a(x1, y1)

#define	FUNC39c(x2, y2)	FUNC39b(x2, y2)
#define	FUNC40c(x2, y2)	FUNC40b(x2, y2)

#include FUNC39c(ZERO, ONE)
#include FUNC40c(ZERO, ONE)

/* Make sure we don't die if the expansion isn't a string. */
#define FUNC_INTEGER(x)       1

/* Make sure one-character names are recognized. */
#define _(x)       translate(x)
#undef _
"""


token_pasting_input = """
#define PASTE_QUOTE(q, name)	q##name##-yes##q
#define PASTE_ANGLE(name)	<##name##-yes>

#define FUNC41	PASTE_QUOTE(", file41)
#define FUNC42	PASTE_ANGLE(file42)

#include FUNC41
#include FUNC42
"""


no_space_input = """
#include<file43-yes>
#include"file44-yes"
"""


nested_ifs_input = """
#define DEFINED

#ifdef	NOT_DEFINED
    #include "file7-no"
    #ifdef DEFINED
        #include "file8-no"
    #else
        #include "file9-no"
    #endif
#else
    #include "file7-yes"
#endif
"""



ifndef_input = """
#define DEFINED 0

#ifndef	DEFINED
#include "file45-no"
#else
#include "file45-yes"
#endif

#ifndef	NOT_DEFINED
#include <file46-yes>
#else
#include <file46-no>
#endif
"""


if_defined_no_space_input = """
#define DEFINED 0

#if(defined DEFINED)
#include "file47-yes"
#endif

#if(!defined DEFINED)
#include <file48-no>
#elif(!defined DEFINED)
#include <file49-no>
#else
#include <file50-yes>
#endif

#if!(defined DEFINED)
#include "file51-no"
#elif!(defined DEFINED)
#include <file52-no>
#else
#include "file53-yes"
#endif
"""

if_no_space_input = """
#define DEFINED 0

#if(DEFINED)
#include "file54-no"
#endif

#if!(DEFINED)
#include <file55-yes>
#elif!(DEFINED)
#include <file56-no>
#endif

#if(DEFINED)
#include "file57-no"
#elif(!DEFINED)
#include <file58-yes>
#endif

#if!( DEFINED)
#include "file59-yes"
#elif!( DEFINED)
#include <file60-no>
#endif

#if( DEFINED)
#include "file61-no"
#elif(! DEFINED)
#include <file62-yes>
#endif
"""


#    pp_class = PreProcessor
#    #pp_class = DumbPreProcessor

#    pp = pp_class(current = ".",
#                  cpppath = ['/usr/include'],
#                  print_all = 1)
#    #pp(open(sys.argv[1]).read())
#    pp(input)


class cppTestCase(unittest.TestCase):
    def setUp(self):
        self.cpp = self.cpp_class(current = ".",
                                  cpppath = ['/usr/include'])

    def test_basic(self):
        """Test basic #include scanning"""
        expect = self.basic_expect
        result = self.cpp.process_contents(basic_input)
        assert expect == result, (expect, result)

    def test_substitution(self):
        """Test substitution of #include files using CPP variables"""
        expect = self.substitution_expect
        result = self.cpp.process_contents(substitution_input)
        assert expect == result, (expect, result)

    def test_ifdef(self):
        """Test basic #ifdef processing"""
        expect = self.ifdef_expect
        result = self.cpp.process_contents(ifdef_input)
        assert expect == result, (expect, result)

    def test_if_boolean(self):
        """Test #if with Boolean values"""
        expect = self.if_boolean_expect
        result = self.cpp.process_contents(if_boolean_input)
        assert expect == result, (expect, result)

    def test_if_defined(self):
        """Test #if defined() idioms"""
        expect = self.if_defined_expect
        result = self.cpp.process_contents(if_defined_input)
        assert expect == result, (expect, result)

    def test_expression(self):
        """Test #if with arithmetic expressions"""
        expect = self.expression_expect
        result = self.cpp.process_contents(expression_input)
        assert expect == result, (expect, result)

    def test_undef(self):
        """Test #undef handling"""
        expect = self.undef_expect
        result = self.cpp.process_contents(undef_input)
        assert expect == result, (expect, result)

    def test_macro_function(self):
        """Test using macro functions to express file names"""
        expect = self.macro_function_expect
        result = self.cpp.process_contents(macro_function_input)
        assert expect == result, (expect, result)

    def test_token_pasting(self):
        """Test token-pasting to construct file names"""
        expect = self.token_pasting_expect
        result = self.cpp.process_contents(token_pasting_input)
        assert expect == result, (expect, result)

    def test_no_space(self):
        """Test no space between #include and the quote"""
        expect = self.no_space_expect
        result = self.cpp.process_contents(no_space_input)
        assert expect == result, (expect, result)

    def test_nested_ifs(self):
        expect = self.nested_ifs_expect
        result = self.cpp.process_contents(nested_ifs_input)
        assert expect == result, (expect, result)

    def test_ifndef(self):
        """Test basic #ifndef processing"""
        expect = self.ifndef_expect
        result = self.cpp.process_contents(ifndef_input)
        assert expect == result, (expect, result)

    def test_if_defined_no_space(self):
        """Test #if(defined, i.e.without space but parenthesis"""
        expect = self.if_defined_no_space_expect
        result = self.cpp.process_contents(if_defined_no_space_input)
        assert expect == result, (expect, result)

    def test_if_no_space(self):
        """Test #if(, i.e. without space but parenthesis"""
        expect = self.if_no_space_expect
        result = self.cpp.process_contents(if_no_space_input)
        assert expect == result, (expect, result)


class cppAllTestCase(cppTestCase):
    def setUp(self):
        self.cpp = self.cpp_class(current = ".",
                                  cpppath = ['/usr/include'],
                                  all=1)

class PreProcessorTestCase(cppAllTestCase):
    cpp_class = cpp.PreProcessor

    basic_expect = [
        ('include', '"', 'file1-yes'),
        ('include', '<', 'file2-yes'),
    ]

    substitution_expect = [
        ('include', '"', 'file3-yes'),
        ('include', '<', 'file4-yes'),
        ('include', '"', 'file5-yes'),
        ('include', '<', 'file6-yes'),
    ]

    ifdef_expect = [
        ('include', '"', 'file7-yes'),
        ('include', '<', 'file8-yes'),
    ]

    if_boolean_expect = [
        ('include', '"', 'file9-yes'),
        ('include', '<', 'file10-yes'),
        ('include', '"', 'file11-yes'),
        ('include', '<', 'file12-yes'),
        ('include', '"', 'file13-yes'),
        ('include', '<', 'file14-yes'),
    ]

    if_defined_expect = [
        ('include', '"', 'file15-yes'),
        ('include', '<', 'file16-yes'),
        ('include', '"', 'file17-yes'),
        ('include', '<', 'file18-yes'),
        ('include', '<', 'file19-yes'),
    ]

    expression_expect = [
        ('include', '"', 'file19-yes'),
        ('include', '<', 'file20-yes'),
        ('include', '"', 'file21-yes'),
        ('include', '<', 'file22-yes'),
        ('include', '"', 'file23-yes'),
        ('include', '<', 'file24-yes'),
        ('include', '"', 'file25-yes'),
        ('include', '<', 'file26-yes'),
        ('include', '"', 'file27-yes'),
        ('include', '<', 'file28-yes'),
        ('include', '"', 'file29-yes'),
        ('include', '<', 'file30-yes'),
        ('include', '<', 'file301-yes'),
    ]

    undef_expect = [
        ('include', '"', 'file31-yes'),
        ('include', '<', 'file32-yes'),
    ]

    macro_function_expect = [
        ('include', '"', 'file33-yes'),
        ('include', '<', 'file34-yes'),
        ('include', '"', 'file35-yes'),
        ('include', '<', 'file36-yes'),
        ('include', '"', 'file37-yes'),
        ('include', '<', 'file38-yes'),
        ('include', '"', 'file39-yes'),
        ('include', '<', 'file40-yes'),
    ]

    token_pasting_expect = [
        ('include', '"', 'file41-yes'),
        ('include', '<', 'file42-yes'),
    ]

    no_space_expect = [
        ('include', '<', 'file43-yes'),
        ('include', '"', 'file44-yes'),
    ]

    nested_ifs_expect = [
        ('include', '"', 'file7-yes'),
    ]

    ifndef_expect = [
        ('include', '"', 'file45-yes'),
        ('include', '<', 'file46-yes'),
    ]

    if_defined_no_space_expect = [
        ('include', '"', 'file47-yes'),
        ('include', '<', 'file50-yes'),
        ('include', '"', 'file53-yes'),
    ]

    if_no_space_expect = [
        ('include', '<', 'file55-yes'),
        ('include', '<', 'file58-yes'),
        ('include', '"', 'file59-yes'),
        ('include', '<', 'file62-yes'),
    ]

class DumbPreProcessorTestCase(cppAllTestCase):
    cpp_class = cpp.DumbPreProcessor

    basic_expect = [
        ('include', '"', 'file1-yes'),
        ('include', '<', 'file2-yes'),
    ]

    substitution_expect = [
        ('include', '"', 'file3-yes'),
        ('include', '<', 'file4-yes'),
        ('include', '"', 'file5-yes'),
        ('include', '<', 'file6-yes'),
    ]

    ifdef_expect = [
        ('include', '"', 'file7-yes'),
        ('include', '"', 'file7-no'),
        ('include', '<', 'file8-no'),
        ('include', '<', 'file8-yes'),
    ]

    if_boolean_expect = [
        ('include', '"', 'file9-no'),
        ('include', '"', 'file9-yes'),
        ('include', '<', 'file10-yes'),
        ('include', '<', 'file10-no'),
        ('include', '"', 'file11-no-1'),
        ('include', '"', 'file11-no-2'),
        ('include', '"', 'file11-yes'),
        ('include', '<', 'file12-no-1'),
        ('include', '<', 'file12-yes'),
        ('include', '<', 'file12-no-2'),
        ('include', '"', 'file13-yes'),
        ('include', '"', 'file13-no-1'),
        ('include', '"', 'file13-no-2'),
        ('include', '<', 'file14-yes'),
        ('include', '<', 'file14-no-1'),
        ('include', '<', 'file14-no-2'),
    ]

    if_defined_expect = [
        ('include', '"', 'file15-yes'),
        ('include', '<', 'file16-no'),
        ('include', '<', 'file16-yes'),
        ('include', '"', 'file17-yes'),
        ('include', '<', 'file18-no'),
        ('include', '<', 'file18-yes'),
        ('include', '<', 'file19-no'),
        ('include', '<', 'file19-yes'),
    ]

    expression_expect = [
        ('include', '"', 'file19-no'),
        ('include', '"', 'file19-yes'),
        ('include', '<', 'file20-no'),
        ('include', '<', 'file20-yes'),
        ('include', '"', 'file21-no'),
        ('include', '"', 'file21-yes'),
        ('include', '<', 'file22-yes'),
        ('include', '<', 'file22-no'),
        ('include', '"', 'file23-no'),
        ('include', '"', 'file23-yes'),
        ('include', '<', 'file24-yes'),
        ('include', '<', 'file24-no'),
        ('include', '"', 'file25-yes'),
        ('include', '"', 'file25-no'),
        ('include', '<', 'file26-yes'),
        ('include', '<', 'file26-no'),
        ('include', '"', 'file27-yes'),
        ('include', '"', 'file27-no'),
        ('include', '<', 'file28-no'),
        ('include', '<', 'file28-yes'),
        ('include', '"', 'file29-no'),
        ('include', '"', 'file29-yes'),
        ('include', '<', 'file30-yes'),
        ('include', '<', 'file30-no'),
        ('include', '<', 'file301-yes'),
        ('include', '<', 'file301-no'),
    ]

    undef_expect = [
        ('include', '"', 'file31-yes'),
        ('include', '"', 'file31-no'),
        ('include', '<', 'file32-no'),
        ('include', '<', 'file32-yes'),
    ]

    macro_function_expect = [
        ('include', '"', 'file33-yes'),
        ('include', '<', 'file34-yes'),
        ('include', '"', 'file35-yes'),
        ('include', '<', 'file36-yes'),
        ('include', '"', 'file37-yes'),
        ('include', '<', 'file38-yes'),
        ('include', '"', 'file39-yes'),
        ('include', '<', 'file40-yes'),
    ]

    token_pasting_expect = [
        ('include', '"', 'file41-yes'),
        ('include', '<', 'file42-yes'),
    ]

    no_space_expect = [
        ('include', '<', 'file43-yes'),
        ('include', '"', 'file44-yes'),
    ]

    nested_ifs_expect = [
        ('include', '"', 'file7-no'),
        ('include', '"', 'file8-no'),
        ('include', '"', 'file9-no'),
        ('include', '"', 'file7-yes')
    ]

    ifndef_expect = [
        ('include', '"', 'file45-no'),
        ('include', '"', 'file45-yes'),
        ('include', '<', 'file46-yes'),
        ('include', '<', 'file46-no'),
    ]

    if_defined_no_space_expect = [
        ('include', '"', 'file47-yes'),
        ('include', '<', 'file48-no'),
        ('include', '<', 'file49-no'),
        ('include', '<', 'file50-yes'),
        ('include', '"', 'file51-no'),
        ('include', '<', 'file52-no'),
        ('include', '"', 'file53-yes'),
    ]

    if_no_space_expect = [
        ('include', '"', 'file54-no'),
        ('include', '<', 'file55-yes'),
        ('include', '<', 'file56-no'),
        ('include', '"', 'file57-no'),
        ('include', '<', 'file58-yes'),
        ('include', '"', 'file59-yes'),
        ('include', '<', 'file60-no'),
        ('include', '"', 'file61-no'),
        ('include', '<', 'file62-yes'),
    ]

import os
import re
import shutil
import tempfile

_Cleanup = []

def _clean():
    for dir in _Cleanup:
        if os.path.exists(dir):
            shutil.rmtree(dir)

atexit.register(_clean)

if os.name in ('posix', 'nt'):
    tmpprefix = 'cppTests.' + str(os.getpid()) + '.'
else:
    tmpprefix = 'cppTests.'

class fileTestCase(unittest.TestCase):
    cpp_class = cpp.DumbPreProcessor

    def setUp(self):
        path = tempfile.mkdtemp(prefix=tmpprefix)
        _Cleanup.append(path)
        self.tempdir = path
        self.orig_cwd = os.getcwd()
        os.chdir(path)

    def tearDown(self):
        os.chdir(self.orig_cwd)
        shutil.rmtree(self.tempdir)
        _Cleanup.remove(self.tempdir)

    def strip_initial_spaces(self, s):
        lines = s.split('\n')
        spaces = re.match(' *', lines[0]).group(0)
        def strip_spaces(l, spaces=spaces):
            if l[:len(spaces)] == spaces:
                l = l[len(spaces):]
            return l
        return '\n'.join(map(strip_spaces, lines))

    def write(self, file, contents):
        with open(file, 'w') as f:
            f.write(self.strip_initial_spaces(contents))

    def test_basic(self):
        """Test basic file inclusion"""
        self.write('f1.h', """\
        #include "f2.h"
        """)
        self.write('f2.h', """\
        #include <f3.h>
        """)
        self.write('f3.h', """\
        """)
        p = cpp.DumbPreProcessor(current = os.curdir,
                                 cpppath = [os.curdir])
        result = p('f1.h')
        assert result == ['f2.h', 'f3.h'], result

    def test_current_file(self):
        """Test use of the .current_file attribute"""
        self.write('f1.h', """\
        #include <f2.h>
        """)
        self.write('f2.h', """\
        #include "f3.h"
        """)
        self.write('f3.h', """\
        """)
        class MyPreProcessor(cpp.DumbPreProcessor):
            def __init__(self, *args, **kw):
                super().__init__(*args, **kw)
                self.files = []
            def __call__(self, file):
                self.files.append(file)
                return cpp.DumbPreProcessor.__call__(self, file)
            def scons_current_file(self, t):
                r = cpp.DumbPreProcessor.scons_current_file(self, t)
                self.files.append(self.current_file)
                return r
        p = MyPreProcessor(current = os.curdir, cpppath = [os.curdir])
        result = p('f1.h')
        assert result == ['f2.h', 'f3.h'], result
        assert p.files == ['f1.h', 'f2.h', 'f3.h', 'f2.h', 'f1.h'], p.files



if __name__ == '__main__':
    suite = unittest.TestSuite()
    tclasses = [ PreProcessorTestCase,
                 DumbPreProcessorTestCase,
                 fileTestCase,
               ]
    for tclass in tclasses:
        loader = unittest.TestLoader()
        loader.testMethodPrefix = 'test_'
        names = loader.getTestCaseNames(tclass)
        try:
            names = sorted(set(names))
        except NameError:
            pass
        suite.addTests(list(map(tclass, names)))
    TestUnit.run(suite)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/dblite.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""
dblite.py module contributed by Ralf W. Grosse-Kunstleve.
Extended for Unicode by Steven Knight.
"""

import os
import pickle
import shutil
import time

from SCons.compat import PICKLE_PROTOCOL

KEEP_ALL_FILES = False
IGNORE_CORRUPT_DBFILES = False


def corruption_warning(filename):
    """Local warning for corrupt db.

    Used for self-tests. SCons overwrites this with a
    different warning function in SConsign.py.
    """
    print("Warning: Discarding corrupt database:", filename)

DBLITE_SUFFIX = '.dblite'
TMP_SUFFIX = '.tmp'


class dblite:
    """
    Squirrel away references to the functions in various modules
    that we'll use when our __del__() method calls our sync() method
    during shutdown.  We might get destroyed when Python is in the midst
    of tearing down the different modules we import in an essentially
    arbitrary order, and some of the various modules's global attributes
    may already be wiped out from under us.

    See the discussion at:
      http://mail.python.org/pipermail/python-bugs-list/2003-March/016877.html

    """

    _open = open
    _pickle_dump = staticmethod(pickle.dump)
    _pickle_protocol = PICKLE_PROTOCOL

    try:
        _os_chown = os.chown
    except AttributeError:
        _os_chown = None
    _os_replace = os.replace
    _os_chmod = os.chmod
    _shutil_copyfile = shutil.copyfile
    _time_time = time.time

    def __init__(self, file_base_name, flag, mode):
        assert flag in (None, "r", "w", "c", "n")
        if flag is None:
            flag = "r"

        base, ext = os.path.splitext(file_base_name)
        if ext == DBLITE_SUFFIX:
            # There's already a suffix on the file name, don't add one.
            self._file_name = file_base_name
            self._tmp_name = base + TMP_SUFFIX
        else:
            self._file_name = file_base_name + DBLITE_SUFFIX
            self._tmp_name = file_base_name + TMP_SUFFIX

        self._flag = flag
        self._mode = mode
        self._dict = {}
        self._needs_sync = False

        if self._os_chown is not None and (os.geteuid() == 0 or os.getuid() == 0):
            # running as root; chown back to current owner/group when done
            try:
                statinfo = os.stat(self._file_name)
                self._chown_to = statinfo.st_uid
                self._chgrp_to = statinfo.st_gid
            except OSError:
                # db file doesn't exist yet.
                # Check os.environ for SUDO_UID, use if set
                self._chown_to = int(os.environ.get('SUDO_UID', -1))
                self._chgrp_to = int(os.environ.get('SUDO_GID', -1))
        else:
            self._chown_to = -1  # don't chown
            self._chgrp_to = -1  # don't chgrp

        if self._flag == "n":
            with self._open(self._file_name, "wb", self._mode):
                pass  # just make sure it exists
        else:
            try:
                f = self._open(self._file_name, "rb")
            except IOError as e:
                if self._flag != "c":
                    raise e
                with self._open(self._file_name, "wb", self._mode):
                    pass  # just make sure it exists
            else:
                p = f.read()
                f.close()
                if len(p) > 0:
                    try:
                        self._dict = pickle.loads(p, encoding='bytes')
                    except (pickle.UnpicklingError, EOFError, KeyError):
                        # Note how we catch KeyErrors too here, which might happen
                        # when we don't have cPickle available (default pickle
                        # throws it).
                        if IGNORE_CORRUPT_DBFILES:
                            corruption_warning(self._file_name)
                        else:
                            raise

    def close(self):
        if self._needs_sync:
            self.sync()

    def __del__(self):
        self.close()

    def sync(self):
        self._check_writable()
        with self._open(self._tmp_name, "wb", self._mode) as f:
            self._pickle_dump(self._dict, f, self._pickle_protocol)

        try:
            self._os_replace(self._tmp_name, self._file_name)
        except PermissionError:
            # If we couldn't replace due to perms, try to change and retry.
            # This is mainly for Windows - on POSIX the file permissions
            # don't matter, the os.replace would have worked anyway.
            # We're giving up if the retry fails, just let the Python
            # exception abort us.
            try:
                self._os_chmod(self._file_name, 0o777)
            except PermissionError:
                pass
            self._os_replace(self._tmp_name, self._file_name)

        if self._os_chown is not None and self._chown_to > 0:  # don't chown to root or -1
            try:
                self._os_chown(self._file_name, self._chown_to, self._chgrp_to)
            except OSError:
                pass

        self._needs_sync = False
        if KEEP_ALL_FILES:
            self._shutil_copyfile(
                self._file_name,
                self._file_name + "_" + str(int(self._time_time()))
            )

    def _check_writable(self):
        if self._flag == "r":
            raise IOError("Read-only database: %s" % self._file_name)

    def __getitem__(self, key):
        return self._dict[key]

    def __setitem__(self, key, value):
        self._check_writable()

        if not isinstance(key, str):
            raise TypeError("key `%s' must be a string but is %s" % (key, type(key)))

        if not isinstance(value, bytes):
            raise TypeError("value `%s' must be a bytes but is %s" % (value, type(value)))

        self._dict[key] = value
        self._needs_sync = True

    def keys(self):
        return list(self._dict.keys())

    def __contains__(self, key):
        return key in self._dict

    def __iter__(self):
        return iter(self._dict)

    def __len__(self):
        return len(self._dict)


def open(file, flag=None, mode=0o666):
    return dblite(file, flag, mode)


def _exercise():
    db = open("tmp", "n")
    assert len(db) == 0
    db["foo"] = b"bar"
    assert db["foo"] == b"bar"
    db.sync()

    db = open("tmp", "c")
    assert len(db) == 1, len(db)
    assert db["foo"] == b"bar"
    db["bar"] = b"foo"
    assert db["bar"] == b"foo"
    db.sync()

    db = open("tmp", "r")
    assert len(db) == 2, len(db)
    assert db["foo"] == b"bar"
    assert db["bar"] == b"foo"
    try:
        db.sync()
    except IOError as e:
        assert str(e) == "Read-only database: tmp.dblite"
    else:
        raise RuntimeError("IOError expected.")
    db = open("tmp", "w")
    assert len(db) == 2, len(db)
    db["ping"] = b"pong"
    db.sync()

    try:
        db[(1, 2)] = "tuple"
    except TypeError as e:
        assert str(e) == "key `(1, 2)' must be a string but is <class 'tuple'>", str(e)
    else:
        raise RuntimeError("TypeError exception expected")

    try:
        db["list"] = [1, 2]
    except TypeError as e:
        assert str(e) == "value `[1, 2]' must be a bytes but is <class 'list'>", str(e)
    else:
        raise RuntimeError("TypeError exception expected")

    db = open("tmp", "r")
    assert len(db) == 3, len(db)

    db = open("tmp", "n")
    assert len(db) == 0, len(db)
    dblite._open("tmp.dblite", "w")

    db = open("tmp", "r")
    dblite._open("tmp.dblite", "w").write("x")
    try:
        db = open("tmp", "r")
    except pickle.UnpicklingError:
        pass
    else:
        raise RuntimeError("pickle exception expected.")

    global IGNORE_CORRUPT_DBFILES
    IGNORE_CORRUPT_DBFILES = True
    db = open("tmp", "r")
    assert len(db) == 0, len(db)
    os.unlink("tmp.dblite")
    try:
        db = open("tmp", "w")
    except IOError as e:
        assert str(e) == "[Errno 2] No such file or directory: 'tmp.dblite'", str(e)
    else:
        raise RuntimeError("IOError expected.")

    print("Completed _exercise()")


if __name__ == "__main__":
    _exercise()

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/exitfuncs.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""Register functions which are executed when SCons exits for any reason."""

# This is obsolete now

import atexit

_exithandlers = []
def _run_exitfuncs():
    """run any registered exit functions

    _exithandlers is traversed in reverse order so functions are executed
    last in, first out.
    """

    while _exithandlers:
        func, targs, kargs =  _exithandlers.pop()
        func(*targs, **kargs)

def register(func, *targs, **kargs):
    """register a function to be executed upon normal program termination

    func - function to be called at exit
    targs - optional arguments to pass to func
    kargs - optional keyword arguments to pass to func
    """
    _exithandlers.append((func, targs, kargs))


# make our exit function get run by python when it exits
atexit.register(_run_exitfuncs)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/386asm.py

"""SCons.Tool.386asm

Tool specification for the 386ASM assembler for the Phar Lap ETS embedded
operating system.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

from SCons.Tool.PharLapCommon import addPharLapPaths
import SCons.Util

as_module = __import__('as', globals(), locals(), [], 1)

def generate(env):
    """Add Builders and construction variables for ar to an Environment."""
    as_module.generate(env)

    env['AS']        = '386asm'
    env['ASFLAGS']   = SCons.Util.CLVar('')
    env['ASPPFLAGS'] = '$ASFLAGS'
    env['ASCOM']     = '$AS $ASFLAGS $SOURCES -o $TARGET'
    env['ASPPCOM']   = '$CC $ASPPFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS $SOURCES -o $TARGET'

    addPharLapPaths(env)

def exists(env):
    return env.Detect('386asm')

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/DCommon.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""SCons.Tool.DCommon

Common code for the various D tools.

Coded by Russel Winder (russel@winder.org.uk)
2012-09-06
"""

import os.path


def isD(env, source):
    if not source:
        return 0
    for s in source:
        if s.sources:
            ext = os.path.splitext(str(s.sources[0]))[1]
            if ext == '.d':
                return 1
    return 0


def addDPATHToEnv(env, executable):
    dPath = env.WhereIs(executable)
    if dPath:
        phobosDir = dPath[:dPath.rindex(executable)] + '/../src/phobos'
        if os.path.isdir(phobosDir):
            env.Append(DPATH=[phobosDir])


def allAtOnceEmitter(target, source, env):
    if env['DC'] in ('ldc2', 'dmd'):
        env.SideEffect(str(target[0]) + '.o', target[0])
        env.Clean(target[0], str(target[0]) + '.o')
    return target, source

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/FortranCommon.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""

Stuff for processing Fortran, common to all fortran dialects.

"""

import re
import os.path

import SCons.Action
import SCons.Scanner.Fortran
import SCons.Tool
import SCons.Util


def isfortran(env, source):
    """Return 1 if any of code in source has fortran files in it, 0
    otherwise."""
    try:
        fsuffixes = env['FORTRANSUFFIXES']
    except KeyError:
        # If no FORTRANSUFFIXES, no fortran tool, so there is no need to look
        # for fortran sources.
        return 0

    if not source:
        # Source might be None for unusual cases like SConf.
        return 0
    for s in source:
        if s.sources:
            ext = os.path.splitext(str(s.sources[0]))[1]
            if ext in fsuffixes:
                return 1
    return 0


def _fortranEmitter(target, source, env):
    node = source[0].rfile()
    if not node.exists() and not node.is_derived():
       print("Could not locate " + str(node.name))
       return ([], [])
    # This has to match the def_regex in the Fortran scanner
    mod_regex = r"""(?i)^\s*MODULE\s+(?!PROCEDURE|SUBROUTINE|FUNCTION|PURE|ELEMENTAL)(\w+)"""
    cre = re.compile(mod_regex,re.M)
    # Retrieve all USE'd module names
    modules = cre.findall(node.get_text_contents())
    # Remove unique items from the list
    modules = SCons.Util.unique(modules)
    # Convert module name to a .mod filename
    suffix = env.subst('$FORTRANMODSUFFIX', target=target, source=source)
    moddir = env.subst('$FORTRANMODDIR', target=target, source=source)
    modules = [x.lower() + suffix for x in modules]
    for m in modules:
       target.append(env.fs.File(m, moddir))
    return (target, source)


def FortranEmitter(target, source, env):
    import SCons.Defaults
    target, source = _fortranEmitter(target, source, env)
    return SCons.Defaults.StaticObjectEmitter(target, source, env)


def ShFortranEmitter(target, source, env):
    import SCons.Defaults
    target, source = _fortranEmitter(target, source, env)
    return SCons.Defaults.SharedObjectEmitter(target, source, env)


def ComputeFortranSuffixes(suffixes, ppsuffixes):
    """suffixes are fortran source files, and ppsuffixes the ones to be
    pre-processed. Both should be sequences, not strings."""
    assert len(suffixes) > 0
    s = suffixes[0]
    sup = s.upper()
    upper_suffixes = [_.upper() for _ in suffixes]
    if SCons.Util.case_sensitive_suffixes(s, sup):
        ppsuffixes.extend(upper_suffixes)
    else:
        suffixes.extend(upper_suffixes)


def CreateDialectActions(dialect):
    """Create dialect specific actions."""
    CompAction = SCons.Action.Action('$%sCOM ' % dialect, '$%sCOMSTR' % dialect)
    CompPPAction = SCons.Action.Action('$%sPPCOM ' % dialect, '$%sPPCOMSTR' % dialect)
    ShCompAction = SCons.Action.Action('$SH%sCOM ' % dialect, '$SH%sCOMSTR' % dialect)
    ShCompPPAction = SCons.Action.Action('$SH%sPPCOM ' % dialect, '$SH%sPPCOMSTR' % dialect)

    return CompAction, CompPPAction, ShCompAction, ShCompPPAction


def DialectAddToEnv(env, dialect, suffixes, ppsuffixes, support_module=False):
    """Add dialect specific construction variables."""
    ComputeFortranSuffixes(suffixes, ppsuffixes)

    fscan = SCons.Scanner.Fortran.FortranScan("%sPATH" % dialect)

    for suffix in suffixes + ppsuffixes:
        SCons.Tool.SourceFileScanner.add_scanner(suffix, fscan)

    env.AppendUnique(FORTRANSUFFIXES = suffixes + ppsuffixes)

    compaction, compppaction, shcompaction, shcompppaction = \
            CreateDialectActions(dialect)

    static_obj, shared_obj = SCons.Tool.createObjBuilders(env)

    for suffix in suffixes:
        static_obj.add_action(suffix, compaction)
        shared_obj.add_action(suffix, shcompaction)
        static_obj.add_emitter(suffix, FortranEmitter)
        shared_obj.add_emitter(suffix, ShFortranEmitter)

    for suffix in ppsuffixes:
        static_obj.add_action(suffix, compppaction)
        shared_obj.add_action(suffix, shcompppaction)
        static_obj.add_emitter(suffix, FortranEmitter)
        shared_obj.add_emitter(suffix, ShFortranEmitter)

    if '%sFLAGS' % dialect not in env:
        env['%sFLAGS' % dialect] = SCons.Util.CLVar('')

    if 'SH%sFLAGS' % dialect not in env:
        env['SH%sFLAGS' % dialect] = SCons.Util.CLVar('$%sFLAGS' % dialect)

    # If a tool does not define fortran prefix/suffix for include path, use C ones
    if 'INC%sPREFIX' % dialect not in env:
        env['INC%sPREFIX' % dialect] = '$INCPREFIX'

    if 'INC%sSUFFIX' % dialect not in env:
        env['INC%sSUFFIX' % dialect] = '$INCSUFFIX'

    env['_%sINCFLAGS' % dialect] = '${_concat(INC%sPREFIX, %sPATH, INC%sSUFFIX, __env__, RDirs, TARGET, SOURCE, affect_signature=False)}' % (dialect, dialect, dialect)

    if support_module:
        env['%sCOM' % dialect]     = '$%s -o $TARGET -c $%sFLAGS $_%sINCFLAGS $_FORTRANMODFLAG $SOURCES' % (dialect, dialect, dialect)
        env['%sPPCOM' % dialect]   = '$%s -o $TARGET -c $%sFLAGS $CPPFLAGS $_CPPDEFFLAGS $_%sINCFLAGS $_FORTRANMODFLAG $SOURCES' % (dialect, dialect, dialect)
        env['SH%sCOM' % dialect]    = '$SH%s -o $TARGET -c $SH%sFLAGS $_%sINCFLAGS $_FORTRANMODFLAG $SOURCES' % (dialect, dialect, dialect)
        env['SH%sPPCOM' % dialect]  = '$SH%s -o $TARGET -c $SH%sFLAGS $CPPFLAGS $_CPPDEFFLAGS $_%sINCFLAGS $_FORTRANMODFLAG $SOURCES' % (dialect, dialect, dialect)
    else:
        env['%sCOM' % dialect]     = '$%s -o $TARGET -c $%sFLAGS $_%sINCFLAGS $SOURCES' % (dialect, dialect, dialect)
        env['%sPPCOM' % dialect]   = '$%s -o $TARGET -c $%sFLAGS $CPPFLAGS $_CPPDEFFLAGS $_%sINCFLAGS $SOURCES' % (dialect, dialect, dialect)
        env['SH%sCOM' % dialect]    = '$SH%s -o $TARGET -c $SH%sFLAGS $_%sINCFLAGS $SOURCES' % (dialect, dialect, dialect)
        env['SH%sPPCOM' % dialect]  = '$SH%s -o $TARGET -c $SH%sFLAGS $CPPFLAGS $_CPPDEFFLAGS $_%sINCFLAGS $SOURCES' % (dialect, dialect, dialect)

def add_fortran_to_env(env):
    """Add Builders and construction variables for Fortran to an Environment."""
    try:
        FortranSuffixes = env['FORTRANFILESUFFIXES']
    except KeyError:
        FortranSuffixes = ['.f', '.for', '.ftn']

    #print("Adding %s to fortran suffixes" % FortranSuffixes)
    try:
        FortranPPSuffixes = env['FORTRANPPFILESUFFIXES']
    except KeyError:
        FortranPPSuffixes = ['.fpp', '.FPP']

    DialectAddToEnv(env, "FORTRAN", FortranSuffixes,
                    FortranPPSuffixes, support_module=True)

    env['FORTRANMODPREFIX'] = ''     # like $LIBPREFIX
    env['FORTRANMODSUFFIX'] = '.mod' # like $LIBSUFFIX

    env['FORTRANMODDIR'] = ''          # where the compiler should place .mod files
    env['FORTRANMODDIRPREFIX'] = ''    # some prefix to $FORTRANMODDIR - similar to $INCPREFIX
    env['FORTRANMODDIRSUFFIX'] = ''    # some suffix to $FORTRANMODDIR - similar to $INCSUFFIX
    env['_FORTRANMODFLAG'] = '$( ${_concat(FORTRANMODDIRPREFIX, FORTRANMODDIR, FORTRANMODDIRSUFFIX, __env__, RDirs, TARGET, SOURCE)} $)'

def add_f77_to_env(env):
    """Add Builders and construction variables for f77 to an Environment."""
    try:
        F77Suffixes = env['F77FILESUFFIXES']
    except KeyError:
        F77Suffixes = ['.f77']

    #print("Adding %s to f77 suffixes" % F77Suffixes)
    try:
        F77PPSuffixes = env['F77PPFILESUFFIXES']
    except KeyError:
        F77PPSuffixes = []

    DialectAddToEnv(env, "F77", F77Suffixes, F77PPSuffixes)

def add_f90_to_env(env):
    """Add Builders and construction variables for f90 to an Environment."""
    try:
        F90Suffixes = env['F90FILESUFFIXES']
    except KeyError:
        F90Suffixes = ['.f90']

    #print("Adding %s to f90 suffixes" % F90Suffixes)
    try:
        F90PPSuffixes = env['F90PPFILESUFFIXES']
    except KeyError:
        F90PPSuffixes = []

    DialectAddToEnv(env, "F90", F90Suffixes, F90PPSuffixes,
                    support_module=True)


def add_f95_to_env(env):
    """Add Builders and construction variables for f95 to an Environment."""
    try:
        F95Suffixes = env['F95FILESUFFIXES']
    except KeyError:
        F95Suffixes = ['.f95']

    #print("Adding %s to f95 suffixes" % F95Suffixes)
    try:
        F95PPSuffixes = env['F95PPFILESUFFIXES']
    except KeyError:
        F95PPSuffixes = []

    DialectAddToEnv(env, "F95", F95Suffixes, F95PPSuffixes,
                    support_module=True)


def add_f03_to_env(env):
    """Add Builders and construction variables for f03 to an Environment."""
    try:
        F03Suffixes = env['F03FILESUFFIXES']
    except KeyError:
        F03Suffixes = ['.f03']

    #print("Adding %s to f95 suffixes" % F95Suffixes)
    try:
        F03PPSuffixes = env['F03PPFILESUFFIXES']
    except KeyError:
        F03PPSuffixes = []

    DialectAddToEnv(env, "F03", F03Suffixes, F03PPSuffixes,
                    support_module=True)


def add_f08_to_env(env):
    """Add Builders and construction variables for f08 to an Environment."""
    try:
        F08Suffixes = env['F08FILESUFFIXES']
    except KeyError:
        F08Suffixes = ['.f08']

    try:
        F08PPSuffixes = env['F08PPFILESUFFIXES']
    except KeyError:
        F08PPSuffixes = []

    DialectAddToEnv(env, "F08", F08Suffixes, F08PPSuffixes,
                    support_module=True)


def add_all_to_env(env):
    """Add builders and construction variables for all supported fortran
    dialects."""
    add_fortran_to_env(env)
    add_f77_to_env(env)
    add_f90_to_env(env)
    add_f95_to_env(env)
    add_f03_to_env(env)
    add_f08_to_env(env)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/FortranCommonTests.py

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# from typing import Dict, Any

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import os
import os.path
import unittest

import SCons.Node.FS
import SCons.Warnings
import SCons.Tool.FortranCommon

import TestCmd

original = os.getcwd()

test = TestCmd.TestCmd(workdir='')

os.chdir(test.workpath(''))


class DummyEnvironment:
    dictionary = None  # type: Dict[Any, Any]

    def __init__(self, list_cpp_path):
        self.path = list_cpp_path
        self.fs = SCons.Node.FS.FS(test.workpath(''))
        self.dictionary = {}

    def __contains__(self, key):
        return key in self.dictionary

    def __getitem__(self, key):
        return self.dictionary[key]

    def __setitem__(self, key, value):
        self.dictionary[key] = value

    def __delitem__(self, key):
        del self.dictionary[key]

    def subst(self, arg, target=None, source=None, conv=None):
        if arg[0] == '$':
            return self[arg[1:]]
        return arg

    def subst_path(self, path, target=None, source=None, conv=None):
        if not isinstance(path, list):
            path = [path]
        return list(map(self.subst, path))

    def get_calculator(self):
        return None

    def get_factory(self, factory):
        return factory or self.fs.File

    def Dir(self, filename):
        return self.fs.Dir(filename)

    def File(self, filename):
        return self.fs.File(filename)


class FortranScannerSubmodulesTestCase(unittest.TestCase):
    def runTest(self):
        """
        Check that test_1.f90 and test_2.f90 which have interface specifications
        Don't generate targets for those modules listed in the interface section
        """

        test.dir_fixture('fortran_unittests')
        env = DummyEnvironment([test.workpath('modules')])
        env['FORTRANMODDIR'] = 'modules'
        env['FORTRANMODSUFFIX'] = '.mod'
        emitter = SCons.Tool.FortranCommon._fortranEmitter
        # path = s.path(env)

        for fort in ['test_1.f90', 'test_2.f90']:
            file_base, _ = os.path.splitext(fort)
            file_mod = '%s.mod' % file_base
            f = env.File(fort)
            (target, source) = emitter([], [f, ], env)

            # print("Targets:%s\nSources:%s"%([str(a) for a in target], [str(a) for a in source]))

            # should only be 1 target and 1 source
            self.assertEqual(len(target), 1,
                             msg="More than 1 target: %d [%s]" % (len(target), [str(t) for t in target]))
            self.assertEqual(len(source), 1,
                             msg="More than 1 source: %d [%s]" % (len(source), [str(t) for t in source]))

            # target should be file_base.mod
            self.assertEqual(str(target[0]).endswith(file_mod), True,
                             msg="Target[0]=%s doesn't end with '%s'" % (str(target[0]), file_mod))

            # source should be file_base .f90
            self.assertEqual(str(source[0]).endswith(fort), True,
                             msg="Source[0]=%s doesn't end with '%s'" % (str(source[0]), fort))


if __name__ == "__main__":
    unittest.main()


# SCons/Tool/GettextCommon.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""Common routines for gettext tools

Used by several tools of `gettext` toolset.
"""

import os
import re

import SCons.Util
import SCons.Warnings

class XgettextToolWarning(SCons.Warnings.SConsWarning):
    pass


class XgettextNotFound(XgettextToolWarning):
    pass


class MsginitToolWarning(SCons.Warnings.SConsWarning):
    pass


class MsginitNotFound(MsginitToolWarning):
    pass


class MsgmergeToolWarning(SCons.Warnings.SConsWarning):
    pass


class MsgmergeNotFound(MsgmergeToolWarning):
    pass


class MsgfmtToolWarning(SCons.Warnings.SConsWarning):
    pass


class MsgfmtNotFound(MsgfmtToolWarning):
    pass


SCons.Warnings.enableWarningClass(XgettextToolWarning)
SCons.Warnings.enableWarningClass(XgettextNotFound)
SCons.Warnings.enableWarningClass(MsginitToolWarning)
SCons.Warnings.enableWarningClass(MsginitNotFound)
SCons.Warnings.enableWarningClass(MsgmergeToolWarning)
SCons.Warnings.enableWarningClass(MsgmergeNotFound)
SCons.Warnings.enableWarningClass(MsgfmtToolWarning)
SCons.Warnings.enableWarningClass(MsgfmtNotFound)


class _POTargetFactory:
    """ A factory of `PO` target files.

    Factory defaults differ from these of `SCons.Node.FS.FS`.  We set `precious`
    (this is required by builders and actions gettext) and `noclean` flags by
    default for all produced nodes.
    """

    def __init__(self, env, nodefault=True, alias=None, precious=True
                 , noclean=True):
        """ Object constructor.

        **Arguments**

            - *env* (`SCons.Environment.Environment`)
            - *nodefault* (`boolean`) - if `True`, produced nodes will be ignored
              from default target `'.'`
            - *alias* (`string`) - if provided, produced nodes will be automatically
              added to this alias, and alias will be set as `AlwaysBuild`
            - *precious* (`boolean`) - if `True`, the produced nodes will be set as
              `Precious`.
            - *noclen* (`boolean`) - if `True`, the produced nodes will be excluded
              from `Clean`.
        """
        self.env = env
        self.alias = alias
        self.precious = precious
        self.noclean = noclean
        self.nodefault = nodefault

    def _create_node(self, name, factory, directory=None, create=1):
        """ Create node, and set it up to factory settings. """
        node = factory(name, directory, create)
        node.set_noclean(self.noclean)
        node.set_precious(self.precious)
        if self.nodefault:
            self.env.Ignore('.', node)
        if self.alias:
            self.env.AlwaysBuild(self.env.Alias(self.alias, node))
        return node

    def Entry(self, name, directory=None, create=1):
        """ Create `SCons.Node.FS.Entry` """
        return self._create_node(name, self.env.fs.Entry, directory, create)

    def File(self, name, directory=None, create=1):
        """ Create `SCons.Node.FS.File` """
        return self._create_node(name, self.env.fs.File, directory, create)


_re_comment = re.compile(r'(#[^\n\r]+)$', re.M)
_re_lang = re.compile(r'([a-zA-Z0-9_]+)', re.M)


def _read_linguas_from_files(env, linguas_files=None):
    """ Parse `LINGUAS` file and return list of extracted languages """
    global _re_comment
    global _re_lang
    if not SCons.Util.is_List(linguas_files) \
            and not SCons.Util.is_String(linguas_files) \
            and not isinstance(linguas_files, SCons.Node.FS.Base) \
            and linguas_files:
        # If, linguas_files==True or such, then read 'LINGUAS' file.
        linguas_files = ['LINGUAS']
    if linguas_files is None:
        return []
    fnodes = env.arg2nodes(linguas_files)
    linguas = []
    for fnode in fnodes:
        contents = _re_comment.sub("", fnode.get_text_contents())
        ls = [l for l in _re_lang.findall(contents) if l]
        linguas.extend(ls)
    return linguas


from SCons.Builder import BuilderBase


class _POFileBuilder(BuilderBase):
    """ `PO` file builder.

    This is multi-target single-source builder. In typical situation the source
    is single `POT` file, e.g. `messages.pot`, and there are multiple `PO`
    targets to be updated from this `POT`. We must run
    `SCons.Builder.BuilderBase._execute()` separatelly for each target to track
    dependencies separatelly for each target file.

    **NOTE**: if we call `SCons.Builder.BuilderBase._execute(.., target, ...)`
    with target being list of all targets, all targets would be rebuilt each time
    one of the targets from this list is missing. This would happen, for example,
    when new language `ll` enters `LINGUAS_FILE` (at this moment there is no
    `ll.po` file yet). To avoid this, we override
    `SCons.Builder.BuilerBase._execute()` and call it separatelly for each
    target. Here we also append to the target list the languages read from
    `LINGUAS_FILE`.
    """

    #
    # * The argument for overriding _execute(): We must use environment with
    #  builder overrides applied (see BuilderBase.__init__(). Here it comes for
    #  free.
    # * The argument against using 'emitter': The emitter is called too late
    #  by BuilderBase._execute(). If user calls, for example:
    #
    #    env.POUpdate(LINGUAS_FILE = 'LINGUAS')
    #
    #  the builder throws error, because it is called with target=None,
    #  source=None and is trying to "generate" sources or target list first.
    #  If user calls
    #
    #    env.POUpdate(['foo', 'baz'], LINGUAS_FILE = 'LINGUAS')
    #
    #  the env.BuilderWrapper() calls our builder with target=None,
    #  source=['foo', 'baz']. The BuilderBase._execute() then splits execution
    #  and execute iterativelly (recursion) self._execute(None, source[i]).
    #  After that it calls emitter (which is quite too late). The emitter is
    #  also called in each iteration, what makes things yet worse.
    def __init__(self, env, **kw):
        if 'suffix' not in kw:
            kw['suffix'] = '$POSUFFIX'
        if 'src_suffix' not in kw:
            kw['src_suffix'] = '$POTSUFFIX'
        if 'src_builder' not in kw:
            kw['src_builder'] = '_POTUpdateBuilder'
        if 'single_source' not in kw:
            kw['single_source'] = True
        alias = None
        if 'target_alias' in kw:
            alias = kw['target_alias']
            del kw['target_alias']
        if 'target_factory' not in kw:
            kw['target_factory'] = _POTargetFactory(env, alias=alias).File
        super().__init__(**kw)

    def _execute(self, env, target, source, *args, **kw):
        """ Execute builder's actions.

        Here we append to `target` the languages read from `$LINGUAS_FILE` and
        apply `SCons.Builder.BuilderBase._execute()` separatelly to each target.
        The arguments and return value are same as for
        `SCons.Builder.BuilderBase._execute()`.
        """
        import SCons.Node
        linguas_files = None
        if 'LINGUAS_FILE' in env and env['LINGUAS_FILE']:
            linguas_files = env['LINGUAS_FILE']
            # This prevents endless recursion loop (we'll be invoked once for
            # each target appended here, we must not extend the list again).
            env['LINGUAS_FILE'] = None
            linguas = _read_linguas_from_files(env, linguas_files)
            if SCons.Util.is_List(target):
                target.extend(linguas)
            elif target is not None:
                target = [target] + linguas
            else:
                target = linguas
        if not target:
            # Let the SCons.BuilderBase to handle this patologic situation
            return BuilderBase._execute(self, env, target, source, *args, **kw)
        # The rest is ours
        if not SCons.Util.is_List(target):
            target = [target]
        result = []
        for tgt in target:
            r = BuilderBase._execute(self, env, [tgt], source, *args, **kw)
            result.extend(r)
        if linguas_files is not None:
            env['LINGUAS_FILE'] = linguas_files
        return SCons.Node.NodeList(result)


def _translate(env, target=None, source=SCons.Environment._null, *args, **kw):
    """ Function for `Translate()` pseudo-builder """
    if target is None: target = []
    pot = env.POTUpdate(None, source, *args, **kw)
    po = env.POUpdate(target, pot, *args, **kw)
    return po


class RPaths:
    """ Callable object, which returns pathnames relative to SCons current
    working directory.

    It seems like `SCons.Node.FS.Base.get_path()` returns absolute paths
    for nodes that are outside of current working directory (`env.fs.getcwd()`).
    Here, we often have `SConscript`, `POT` and `PO` files within `po/`
    directory and source files (e.g. `*.c`) outside of it. When generating `POT`
    template file, references to source files are written to `POT` template, so
    a translator may later quickly jump to appropriate source file and line from
    its `PO` editor (e.g. `poedit`).  Relative paths in  `PO` file are usually
    interpreted by `PO` editor as paths relative to the place, where `PO` file
    lives. The absolute paths would make resultant `POT` file nonportable, as
    the references would be correct only on the machine, where `POT` file was
    recently re-created. For such reason, we need a function, which always
    returns relative paths. This is the purpose of `RPaths` callable object.

    The `__call__` method returns paths relative to current working directory, but
    we assume, that *xgettext(1)* is run from the directory, where target file is
    going to be created.

    Note, that this may not work for files distributed over several hosts or
    across different drives on windows. We assume here, that single local
    filesystem holds both source files and target `POT` templates.

    Intended use of `RPaths` - in `xgettext.py`::

      def generate(env):
          from GettextCommon import RPaths
          ...
          sources = '$( ${_concat( "", SOURCES, "", __env__, XgettextRPaths, TARGET, SOURCES)} $)'
          env.Append(
            ...
            XGETTEXTCOM = 'XGETTEXT ... ' + sources,
            ...
            XgettextRPaths = RPaths(env)
          )
    """

    # NOTE: This callable object returns pathnames of dirs/files relative to
    # current working directory. The pathname remains relative also for entries
    # that are outside of current working directory (node, that
    # SCons.Node.FS.File and siblings return absolute path in such case). For
    # simplicity we compute path relative to current working directory, this
    # seems be enough for our purposes (don't need TARGET variable and
    # SCons.Defaults.Variable_Caller stuff).

    def __init__(self, env):
        """ Initialize `RPaths` callable object.

          **Arguments**:

            - *env* - a `SCons.Environment.Environment` object, defines *current
              working dir*.
        """
        self.env = env

    # FIXME: I'm not sure, how it should be implemented (what the *args are in
    # general, what is **kw).
    def __call__(self, nodes, *args, **kw):
        """ Return nodes' paths (strings) relative to current working directory.

          **Arguments**:

            - *nodes* ([`SCons.Node.FS.Base`]) - list of nodes.
            - *args* -  currently unused.
            - *kw* - currently unused.

          **Returns**:

           - Tuple of strings, which represent paths relative to current working
             directory (for given environment).
        """
        import SCons.Node.FS
        rpaths = ()
        cwd = self.env.fs.getcwd().get_abspath()
        for node in nodes:
            rpath = None
            if isinstance(node, SCons.Node.FS.Base):
                rpath = os.path.relpath(node.get_abspath(), cwd)
            # FIXME: Other types possible here?
            if rpath is not None:
                rpaths += (rpath,)
        return rpaths


def _init_po_files(target, source, env):
    """ Action function for `POInit` builder. """
    nop = lambda target, source, env: 0
    if 'POAUTOINIT' in env:
        autoinit = env['POAUTOINIT']
    else:
        autoinit = False
    # Well, if everything outside works well, this loop should do single
    # iteration. Otherwise we are rebuilding all the targets even, if just
    # one has changed (but is this our fault?).
    for tgt in target:
        if not tgt.exists():
            if autoinit:
                action = SCons.Action.Action('$MSGINITCOM', '$MSGINITCOMSTR')
            else:
                msg = 'File ' + repr(str(tgt)) + ' does not exist. ' \
                      + 'If you are a translator, you can create it through: \n' \
                      + '$MSGINITCOM'
                action = SCons.Action.Action(nop, msg)
            status = action([tgt], source, env)
            if status: return status
    return 0


def _detect_xgettext(env):
    """ Detects *xgettext(1)* binary """
    if 'XGETTEXT' in env:
        return env['XGETTEXT']
    xgettext = env.Detect('xgettext')
    if xgettext:
        return xgettext
    raise SCons.Errors.StopError(XgettextNotFound, "Could not detect xgettext")
    return None


def _xgettext_exists(env):
    return _detect_xgettext(env)


def _detect_msginit(env):
    """ Detects *msginit(1)* program. """
    if 'MSGINIT' in env:
        return env['MSGINIT']
    msginit = env.Detect('msginit')
    if msginit:
        return msginit
    raise SCons.Errors.StopError(MsginitNotFound, "Could not detect msginit")
    return None


def _msginit_exists(env):
    return _detect_msginit(env)


def _detect_msgmerge(env):
    """ Detects *msgmerge(1)* program. """
    if 'MSGMERGE' in env:
        return env['MSGMERGE']
    msgmerge = env.Detect('msgmerge')
    if msgmerge:
        return msgmerge
    raise SCons.Errors.StopError(MsgmergeNotFound, "Could not detect msgmerge")
    return None


def _msgmerge_exists(env):
    return _detect_msgmerge(env)


def _detect_msgfmt(env):
    """ Detects *msgmfmt(1)* program. """
    if 'MSGFMT' in env:
        return env['MSGFMT']
    msgfmt = env.Detect('msgfmt')
    if msgfmt:
        return msgfmt
    raise SCons.Errors.StopError(MsgfmtNotFound, "Could not detect msgfmt")
    return None


def _msgfmt_exists(env):
    return _detect_msgfmt(env)


def tool_list(platform, env):
    """ List tools that shall be generated by top-level `gettext` tool """
    return ['xgettext', 'msginit', 'msgmerge', 'msgfmt']



# SCons/Tool/JavaCommon.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""Common routines for processing Java. """

import os
import re
import glob
from pathlib import Path
from typing import List

java_parsing = True

default_java_version = '1.4'

# a switch for which jdk versions to use the Scope state for smarter
# anonymous inner class parsing.
scopeStateVersions = ('1.8',)

# Glob patterns for use in finding where the JDK is.
#
# These are pairs, (*dir_glob, *version_dir_glob) depending on whether
# a JDK version was requested or not.
# For now only used for Windows, which doesn't install JDK in a
# path that would be in env['ENV']['PATH'].  The specific tool will
# add the discovered path to this.  Since Oracle changed the rules,
# there are many possible vendors, we can't guess them all, but take a shot.
java_win32_dir_glob = 'C:/Program Files*/*/*jdk*/bin'

# On windows, since Java 9, there is a dash between 'jdk' and the version
# string that wasn't there before. this glob should catch either way.
java_win32_version_dir_glob = 'C:/Program Files*/*/*jdk*%s*/bin'

# Glob patterns for use in finding where the JDK headers are.
# These are pairs, *dir_glob used in the general case,
# *version_dir_glob if matching only a specific version.
java_macos_include_dir_glob = '/System/Library/Frameworks/JavaVM.framework/Headers/'
java_macos_version_include_dir_glob = '/System/Library/Frameworks/JavaVM.framework/Versions/%s*/Headers/'

java_linux_include_dirs_glob = [
    '/usr/lib/jvm/default-java/include',
    '/usr/lib/jvm/java-*/include',
    '/opt/oracle-jdk-bin-*/include',
    '/opt/openjdk-bin-*/include',
    '/usr/lib/openjdk-*/include',
]
# Need to match path like below (from Centos 7)
# /usr/lib/jvm/java-1.8.0-openjdk-1.8.0.191.b12-0.el7_5.x86_64/include/
java_linux_version_include_dirs_glob = [
    '/usr/lib/jvm/java-*-sun-%s*/include',
    '/usr/lib/jvm/java-%s*-openjdk*/include',
    '/usr/java/jdk%s*/include',
]

if java_parsing:
    # Parse Java files for class names.
    #
    # This is a really cool parser from Charles Crain
    # that finds appropriate class names in Java source.

    # A regular expression that will find, in a java file:
    #     newlines;
    #     double-backslashes;
    #     a single-line comment "//";
    #     single or double quotes preceeded by a backslash;
    #     single quotes, double quotes, open or close braces, semi-colons,
    #         periods, open or close parentheses;
    #     floating-point numbers;
    #     any alphanumeric token (keyword, class name, specifier);
    #     any alphanumeric token surrounded by angle brackets (generics);
    #     the multi-line comment begin and end tokens /* and */;
    #     array declarations "[]".
    #     Lambda function symbols: ->
    _reToken = re.compile(r'(\n|\\\\|//|\\[\'"]|[\'"{\};.()]|' +
                          r'\d*\.\d*|[A-Za-z_][\w$.]*|<[A-Za-z_]\w+>|' +
                          r'/\*|\*/|\[\]|->)')


    class OuterState:
        """The initial state for parsing a Java file for classes,
        interfaces, and anonymous inner classes."""

        def __init__(self, version=default_java_version):
            if version not in (
                '1.1',
                '1.2',
                '1.3',
                '1.4',
                '1.5',
                '1.6',
                '1.7',
                '1.8',
                '5',
                '6',
                '9.0',
                '10.0',
                '11.0',
                '12.0',
                '13.0',
                '14.0',
                '15.0',
                '16.0',
                '17.0',
            ):
                msg = "Java version %s not supported" % version
                raise NotImplementedError(msg)

            self.version = version
            self.listClasses = []
            self.listOutputs = []
            self.stackBrackets = []
            self.brackets = 0
            self.nextAnon = 1
            self.localClasses = []
            self.stackAnonClassBrackets = []
            self.anonStacksStack = [[0]]
            self.package = None

        def trace(self):
            pass

        def __getClassState(self):
            try:
                return self.classState
            except AttributeError:
                ret = ClassState(self)
                self.classState = ret
                return ret

        def __getPackageState(self):
            try:
                return self.packageState
            except AttributeError:
                ret = PackageState(self)
                self.packageState = ret
                return ret

        def __getAnonClassState(self):
            try:
                return self.anonState
            except AttributeError:
                self.outer_state = self
                ret = SkipState(1, AnonClassState(self))
                self.anonState = ret
                return ret

        def __getSkipState(self):
            try:
                return self.skipState
            except AttributeError:
                ret = SkipState(1, self)
                self.skipState = ret
                return ret

        def _getAnonStack(self):
            return self.anonStacksStack[-1]

        def openBracket(self):
            self.brackets = self.brackets + 1

        def closeBracket(self):
            self.brackets = self.brackets - 1
            if len(self.stackBrackets) and \
                    self.brackets == self.stackBrackets[-1]:
                self.listOutputs.append('$'.join(self.listClasses))
                self.localClasses.pop()
                self.listClasses.pop()
                self.anonStacksStack.pop()
                self.stackBrackets.pop()
            if len(self.stackAnonClassBrackets) and \
                    self.brackets == self.stackAnonClassBrackets[-1] and \
                    self.version not in scopeStateVersions:
                self._getAnonStack().pop()
                self.stackAnonClassBrackets.pop()

        def parseToken(self, token):
            if token[:2] == '//':
                return IgnoreState('\n', self)
            elif token == '/*':
                return IgnoreState('*/', self)
            elif token == '{':
                self.openBracket()
            elif token == '}':
                self.closeBracket()
            elif token in ['"', "'"]:
                return IgnoreState(token, self)
            elif token == "new":
                # anonymous inner class
                if len(self.listClasses) > 0:
                    return self.__getAnonClassState()
                return self.__getSkipState()  # Skip the class name
            elif token in ['class', 'interface', 'enum']:
                if len(self.listClasses) == 0:
                    self.nextAnon = 1
                self.stackBrackets.append(self.brackets)
                return self.__getClassState()
            elif token == 'package':
                return self.__getPackageState()
            elif token == '.':
                # Skip the attribute, it might be named "class", in which
                # case we don't want to treat the following token as
                # an inner class name...
                return self.__getSkipState()
            return self

        def addAnonClass(self):
            """Add an anonymous inner class"""
            if self.version in ('1.1', '1.2', '1.3', '1.4'):
                clazz = self.listClasses[0]
                self.listOutputs.append('%s$%d' % (clazz, self.nextAnon))
            # TODO: shouldn't need to repeat versions here and in OuterState
            elif self.version in (
                '1.5',
                '1.6',
                '1.7',
                '1.8',
                '5',
                '6',
                '9.0',
                '10.0',
                '11.0',
                '12.0',
                '13.0',
                '14.0',
                '15.0',
                '16.0',
                '17.0',
            ):
                self.stackAnonClassBrackets.append(self.brackets)
                className = []
                className.extend(self.listClasses)
                self._getAnonStack()[-1] = self._getAnonStack()[-1] + 1
                for anon in self._getAnonStack():
                    className.append(str(anon))
                self.listOutputs.append('$'.join(className))

            self.nextAnon = self.nextAnon + 1
            self._getAnonStack().append(0)

        def setPackage(self, package):
            self.package = package


    class ScopeState:
        """
        A state that parses code within a scope normally,
        within the confines of a scope.
        """

        def __init__(self, old_state):
            self.outer_state = old_state.outer_state
            self.old_state = old_state
            self.brackets = 0

        def __getClassState(self):
            try:
                return self.classState
            except AttributeError:
                ret = ClassState(self)
                self.classState = ret
                return ret

        def __getAnonClassState(self):
            try:
                return self.anonState
            except AttributeError:
                ret = SkipState(1, AnonClassState(self))
                self.anonState = ret
                return ret

        def __getSkipState(self):
            try:
                return self.skipState
            except AttributeError:
                ret = SkipState(1, self)
                self.skipState = ret
                return ret

        def openBracket(self):
            self.brackets = self.brackets + 1

        def closeBracket(self):
            self.brackets = self.brackets - 1

        def parseToken(self, token):
            # if self.brackets == 0:
            #     return self.old_state.parseToken(token)
            if token[:2] == '//':
                return IgnoreState('\n', self)
            elif token == '/*':
                return IgnoreState('*/', self)
            elif token == '{':
                self.openBracket()
            elif token == '}':
                self.closeBracket()
                if self.brackets == 0:
                    self.outer_state._getAnonStack().pop()
                    return self.old_state
            elif token in ['"', "'"]:
                return IgnoreState(token, self)
            elif token == "new":
                # anonymous inner class
                return self.__getAnonClassState()
            elif token == '.':
                # Skip the attribute, it might be named "class", in which
                # case we don't want to treat the following token as
                # an inner class name...
                return self.__getSkipState()
            return self


    class AnonClassState:
        """A state that looks for anonymous inner classes."""

        def __init__(self, old_state):
            # outer_state is always an instance of OuterState
            self.outer_state = old_state.outer_state
            self.old_state = old_state
            self.brace_level = 0

        def parseToken(self, token):
            # This is an anonymous class if and only if the next
            # non-whitespace token is a bracket. Everything between
            # braces should be parsed as normal java code.
            if token[:2] == '//':
                return IgnoreState('\n', self)
            elif token == '/*':
                return IgnoreState('*/', self)
            elif token == '\n':
                return self
            elif token[0] == '<' and token[-1] == '>':
                return self
            elif token == '(':
                self.brace_level = self.brace_level + 1
                return self
            if self.brace_level > 0:
                if token == 'new':
                    # look further for anonymous inner class
                    return SkipState(1, AnonClassState(self))
                elif token in ['"', "'"]:
                    return IgnoreState(token, self)
                elif token == ')':
                    self.brace_level = self.brace_level - 1
                return self
            if token == '{':
                self.outer_state.addAnonClass()
                if self.outer_state.version in scopeStateVersions:
                    return ScopeState(old_state=self.old_state).parseToken(token)
            return self.old_state.parseToken(token)


    class SkipState:
        """A state that will skip a specified number of tokens before
        reverting to the previous state."""

        def __init__(self, tokens_to_skip, old_state):
            self.tokens_to_skip = tokens_to_skip
            self.old_state = old_state

        def parseToken(self, token):
            self.tokens_to_skip = self.tokens_to_skip - 1
            if self.tokens_to_skip < 1:
                return self.old_state
            return self


    class ClassState:
        """A state we go into when we hit a class or interface keyword."""

        def __init__(self, outer_state):
            # outer_state is always an instance of OuterState
            self.outer_state = outer_state

        def parseToken(self, token):
            # the next non-whitespace token should be the name of the class
            if token == '\n':
                return self
            # If that's an inner class which is declared in a method, it
            # requires an index prepended to the class-name, e.g.
            # 'Foo$1Inner'
            # https://github.com/SCons/scons/issues/2087
            if self.outer_state.localClasses and \
                    self.outer_state.stackBrackets[-1] > \
                    self.outer_state.stackBrackets[-2] + 1:
                locals = self.outer_state.localClasses[-1]
                try:
                    idx = locals[token]
                    locals[token] = locals[token] + 1
                except KeyError:
                    locals[token] = 1
                token = str(locals[token]) + token
            self.outer_state.localClasses.append({})
            self.outer_state.listClasses.append(token)
            self.outer_state.anonStacksStack.append([0])
            return self.outer_state


    class IgnoreState:
        """A state that will ignore all tokens until it gets to a
        specified token."""

        def __init__(self, ignore_until, old_state):
            self.ignore_until = ignore_until
            self.old_state = old_state

        def parseToken(self, token):
            if self.ignore_until == token:
                return self.old_state
            return self


    class PackageState:
        """The state we enter when we encounter the package keyword.
        We assume the next token will be the package name."""

        def __init__(self, outer_state):
            # outer_state is always an instance of OuterState
            self.outer_state = outer_state

        def parseToken(self, token):
            self.outer_state.setPackage(token)
            return self.outer_state


    def parse_java_file(fn, version=default_java_version):
        with open(fn, 'r', encoding='utf-8') as f:
            data = f.read()
        return parse_java(data, version)


    def parse_java(contents, version=default_java_version, trace=None):
        """Parse a .java file and return a double of package directory,
        plus a list of .class files that compiling that .java file will
        produce"""
        package = None
        initial = OuterState(version)
        currstate = initial
        for token in _reToken.findall(contents):
            # The regex produces a bunch of groups, but only one will
            # have anything in it.
            currstate = currstate.parseToken(token)
            if trace: trace(token, currstate)
        if initial.package:
            package = initial.package.replace('.', os.sep)
        return (package, initial.listOutputs)

else:
    # Don't actually parse Java files for class names.
    #
    # We might make this a configurable option in the future if
    # Java-file parsing takes too long (although it shouldn't relative
    # to how long the Java compiler itself seems to take...).

    def parse_java_file(fn):
        """ "Parse" a .java file.

        This actually just splits the file name, so the assumption here
        is that the file name matches the public class name, and that
        the path to the file is the same as the package name.
        """
        return os.path.split(fn)


def get_java_install_dirs(platform, version=None) -> List[str]:
    """ Find possible java jdk installation directories.

    Returns a list for use as `default_paths` when looking up actual
    java binaries with :meth:`SCons.Tool.find_program_path`.
    The paths are sorted by version, latest first.

    Args:
        platform: selector for search algorithm.
        version: if not None, restrict the search to this version.

    Returns:
        list of default paths for jdk.
    """

    if platform == 'win32':
        paths = []
        if version:
            paths = glob.glob(java_win32_version_dir_glob % version)
        else:
            paths = glob.glob(java_win32_dir_glob)

        def win32getvnum(java):
            """ Generates a sort key for win32 jdk versions.

            We'll have gotten a path like ...something/*jdk*/bin because
            that is the pattern we glob for. To generate the sort key,
            extracts the next-to-last component, then trims it further if
            it had a complex name, like 'java-1.8.0-openjdk-1.8.0.312-1',
            to try and put it on a common footing with the more common style,
            which looks like 'jdk-11.0.2'. 

            This is certainly fragile, and if someone has a 9.0 it won't
            sort right since this will still be alphabetic, BUT 9.0 was
            not an LTS release and is 30 mos out of support as this note
            is written so just assume it will be okay.
            """
            d = Path(java).parts[-2]
            if not d.startswith('jdk'):
                d = 'jdk' + d.rsplit('jdk', 1)[-1]
            return d

        return sorted(paths, key=win32getvnum, reverse=True)

    # other platforms, do nothing for now: we expect the standard
    # paths to be enough to find a jdk (e.g. use alternatives system)
    return []


def get_java_include_paths(env, javac, version) -> List[str]:
    """Find java include paths for JNI building.

    Cannot be called in isolation - `javac` refers to an already detected
    compiler. Normally would would call :func:`get_java_install_dirs` first
    and then do lookups on the paths it returns before calling us.

    Args:
        env: construction environment, used to extract platform.
        javac: path to detected javac.
        version: if not None, restrict the search to this version.

    Returns:
        list of include directory paths.
    """

    if not javac:
        return []

    # on Windows, we have a path to the actual javac, so look locally
    if env['PLATFORM'] == 'win32':
        javac_bin_dir = os.path.dirname(javac)
        java_inc_dir = os.path.normpath(os.path.join(javac_bin_dir, '..', 'include'))
        paths = [java_inc_dir, os.path.join(java_inc_dir, 'win32')]

    # for the others, we probably found something which isn't in the JDK dir,
    # so use the predefined patterns to glob for an include directory.
    elif env['PLATFORM'] == 'darwin':
        if not version:
            paths = [java_macos_include_dir_glob]
        else:
            paths = sorted(glob.glob(java_macos_version_include_dir_glob % version))
    else:
        base_paths = []
        if not version:
            for p in java_linux_include_dirs_glob:
                base_paths.extend(glob.glob(p))
        else:
            for p in java_linux_version_include_dirs_glob:
                base_paths.extend(glob.glob(p % version))

        paths = []
        for p in base_paths:
            paths.extend([p, os.path.join(p, 'linux')])

    return paths

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/JavaCommonTests.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

import os.path
import unittest
import fnmatch

import SCons.Scanner.IDL
import SCons.Tool.JavaCommon

import TestSCons

# Adding trace=trace to any of the parse_jave() calls below will cause
# the parser to spit out trace messages of the tokens it sees and the
# attendant transitions.

def trace(token, newstate):
    from SCons.Debug import Trace
    statename = newstate.__class__.__name__
    Trace('token = %s, state = %s\n' % (repr(token), statename))

class parse_javaTestCase(unittest.TestCase):

    def test_bare_bones(self):
        """Test a bare-bones class"""

        input = """\
package com.sub.bar;

public class Foo
{

     public static void main(String[] args)
     {

        /* This tests a former bug where strings would eat later code. */
        String hello1 = new String("Hello, world!");

     }

}
"""
        pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input)
        assert pkg_dir == os.path.join('com', 'sub', 'bar'), pkg_dir
        assert classes == ['Foo'], classes


    def test_file_parser(self):
        """Test the file parser"""
        input = """\
package com.sub.bar;

public class Foo
{
     public static void main(String[] args)
     {
        /* This tests that unicde is handled . */
        String hello1 = new String("ఎత్తువెడల్పు");
     }
}
"""
        file_name = 'test_file_parser.java'
        with open(file_name, 'w', encoding='UTF-8') as jf:
            print(input, file=jf)

        pkg_dir, classes = SCons.Tool.JavaCommon.parse_java_file(file_name)
        if os.path.exists(file_name):
            os.remove(file_name)
        assert pkg_dir == os.path.join('com', 'sub', 'bar'), pkg_dir
        assert classes == ['Foo'], classes


    def test_dollar_sign(self):
        """Test class names with $ in them"""

        input = """\
public class BadDep {
  public void new$rand () {}
}
"""
        pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input)
        assert pkg_dir is None, pkg_dir
        assert classes == ['BadDep'], classes



    def test_inner_classes(self):
        """Test parsing various forms of inner classes"""

        input = """\
class Empty {
}

interface Listener {
  public void execute();
}

public
class
Test implements Listener {
  class Inner {
    void go() {
      use(new Listener() {
        public void execute() {
          System.out.println("In Inner");
        }
      });
    }
    String s1 = "class A";
    String s2 = "new Listener() { }";
    /* class B */
    /* new Listener() { } */
  }

  class Inner2 {
     Inner2() { Listener l = new Listener(); }
  }

  /* Make sure this class doesn't get interpreted as an inner class of the previous one, when "new" is used in the previous class. */
  class Inner3 {

  }

  public static void main(String[] args) {
    new Test().run();
  }

  void run() {
    use(new Listener() {
      public void execute() {
        use(new Listener( ) {
          public void execute() {
            System.out.println("Inside execute()");
          }
        });
      }
    });

    new Inner().go();
  }

  void use(Listener l) {
    l.execute();
  }
}

class Private {
  void run() {
    new Listener() {
      public void execute() {
      }
    };
  }
}
"""

        pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.4')
        assert pkg_dir is None, pkg_dir
        expect = [
                   'Empty',
                   'Listener',
                   'Test$1',
                   'Test$Inner',
                   'Test$Inner2',
                   'Test$Inner3',
                   'Test$2',
                   'Test$3',
                   'Test',
                   'Private$1',
                   'Private',
                 ]
        assert classes == expect, classes

        pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.5')
        assert pkg_dir is None, pkg_dir
        expect = [
                   'Empty',
                   'Listener',
                   'Test$Inner$1',
                   'Test$Inner',
                   'Test$Inner2',
                   'Test$Inner3',
                   'Test$1',
                   'Test$1$1',
                   'Test',
                   'Private$1',
                   'Private',
                 ]
        assert classes == expect, (expect, classes)

        pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '5')
        assert pkg_dir is None, pkg_dir
        expect = [
                   'Empty',
                   'Listener',
                   'Test$Inner$1',
                   'Test$Inner',
                   'Test$Inner2',
                   'Test$Inner3',
                   'Test$1',
                   'Test$1$1',
                   'Test',
                   'Private$1',
                   'Private',
                 ]
        assert classes == expect, (expect, classes)



    def test_comments(self):
        """Test a class with comments"""

        input = """\
package com.sub.foo;

import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.RMISecurityManager;
import java.rmi.server.UnicastRemoteObject;

public class Example1 extends UnicastRemoteObject implements Hello {

    public Example1() throws RemoteException {
        super();
    }

    public String sayHello() {
        return "Hello World!";
    }

    public static void main(String args[]) {
        if (System.getSecurityManager() == null) {
            System.setSecurityManager(new RMISecurityManager());
        }
        // a comment
        try {
            Example1 obj = new Example1();

            Naming.rebind("//myhost/HelloServer", obj);

            System.out.println("HelloServer bound in registry");
        } catch (Exception e) {
            System.out.println("Example1 err: " + e.getMessage());
            e.printStackTrace();
        }
    }
}
"""

        pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input)
        assert pkg_dir == os.path.join('com', 'sub', 'foo'), pkg_dir
        assert classes == ['Example1'], classes


    def test_arrays(self):
        """Test arrays of class instances"""

        input = """\
public class Test {
    MyClass abc = new MyClass();
    MyClass xyz = new MyClass();
    MyClass _array[] = new MyClass[] {
        abc,
        xyz
    }
}
"""

        pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input)
        assert pkg_dir is None, pkg_dir
        assert classes == ['Test'], classes



    def test_backslash(self):
        """Test backslash handling"""

        input = """\
public class MyTabs
{
        private class MyInternal
        {
        }
        private final static String PATH = "images\\\\";
}
"""

        pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input)
        assert pkg_dir is None, pkg_dir
        assert classes == ['MyTabs$MyInternal', 'MyTabs'], classes


    def test_enum(self):
        """Test the Java 1.5 enum keyword"""

        input = """\
package p;
public enum a {}
"""

        pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input)
        assert pkg_dir == 'p', pkg_dir
        assert classes == ['a'], classes


    def test_anon_classes(self):
        """Test anonymous classes"""

        input = """\
public abstract class TestClass
{
    public void completed()
    {
        new Thread()
        {
        }.start();

        new Thread()
        {
        }.start();
    }
}
"""

        pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input)
        assert pkg_dir is None, pkg_dir
        assert classes == ['TestClass$1', 'TestClass$2', 'TestClass'], classes


    def test_closing_bracket(self):
        """Test finding a closing bracket instead of an anonymous class"""

        input = """\
class TestSCons {
    public static void main(String[] args) {
        Foo[] fooArray = new Foo[] { new Foo() };
    }
}

class Foo { }
"""

        pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input)
        assert pkg_dir is None, pkg_dir
        assert classes == ['TestSCons', 'Foo'], classes


    def test_dot_class_attributes(self):
        """Test handling ".class" attributes"""

        input = """\
public class Test extends Object
{
    static {
        Class c = Object[].class;
        Object[] s = new Object[] {};
    }
}
"""

        pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input)
        assert classes == ['Test'], classes

        input = """\
public class A {
    public class B {
        public void F(Object[] o) {
            F(new Object[] {Object[].class});
        }
        public void G(Object[] o) {
            F(new Object[] {});
        }
    }
}
"""

        pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input)
        assert pkg_dir is None, pkg_dir
        assert classes == ['A$B', 'A'], classes

    def test_anonymous_classes_with_parentheses(self):
        """Test finding anonymous classes marked by parentheses"""

        input = """\
import java.io.File;

public class Foo {
    public static void main(String[] args) {
        File f = new File(
            new File("a") {
                public String toString() {
                    return "b";
                }
            } to String()
        ) {
            public String toString() {
                return "c";
            }
        };
    }
}
"""

        pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.4')
        assert classes == ['Foo$1', 'Foo$2', 'Foo'], classes

        pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.5')
        assert classes == ['Foo$1', 'Foo$1$1', 'Foo'], classes

        pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '6')
        assert classes == ['Foo$1', 'Foo$1$1', 'Foo'], classes



    def test_nested_anonymous_inner_classes(self):
        """Test finding nested anonymous inner classes"""

        input = """\
// import java.util.*;

public class NestedExample
{
        public NestedExample()
        {
                Thread t = new Thread() {
                        public void start()
                        {
                                Thread t = new Thread() {
                                        public void start()
                                        {
                                                try {Thread.sleep(200);}
                                                catch (Exception e) {}
                                        }
                                };
                                while (true)
                                {
                                        try {Thread.sleep(200);}
                                        catch (Exception e) {}
                                }
                        }
                };
        }


        public static void main(String argv[])
        {
                NestedExample e = new NestedExample();
        }
}
"""

        pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.4')
        expect = [ 'NestedExample$1', 'NestedExample$2', 'NestedExample' ]
        assert expect == classes, (expect, classes)

        pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.5')
        expect = [ 'NestedExample$1', 'NestedExample$1$1', 'NestedExample' ]
        assert expect == classes, (expect, classes)

        pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '6')
        expect = [ 'NestedExample$1', 'NestedExample$1$1', 'NestedExample' ]
        assert expect == classes, (expect, classes)

    def test_lambda_after_new(self):
        """Test lamdas after new"""

        input = """\
// import java.util.*;

public class LamdaExample
{

    public void testFunc (int arg1, String arg2, Runnable lambda){
    }
    public LamdaExample()
    {
        testFunc(
            5,
            new String("test"),
            // Lambda symbol is after new, and used curly braces so
            // we should not parse this as a new class.
            () -> {}
        );
    }


    public static void main(String argv[])
    {
            LamdaExample e = new LamdaExample();
    }
}
"""
        pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.4')
        expect = [ 'LamdaExample' ]
        assert expect == classes, (expect, classes)

        pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.8')
        expect = [ 'LamdaExample' ]
        assert expect == classes, (expect, classes)

    def test_private_inner_class_instantiation(self):
        """Test anonymous inner class generated by private instantiation"""

        input = """\
class test
{
    test()
    {
        super();
        new inner();
    }

    static class inner
    {
        private inner() {}
    }
}
"""

        # This is what we *should* generate, apparently due to the
        # private instantiation of the inner class, but don't today.
        #expect = [ 'test$1', 'test$inner', 'test' ]

        # What our parser currently generates, which doesn't match
        # what the Java compiler actually generates.
        expect = [ 'test$inner', 'test' ]

        pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.4')
        assert expect == classes, (expect, classes)

        pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.5')
        assert expect == classes, (expect, classes)

    def test_floating_point_numbers(self):
        """Test floating-point numbers in the input stream"""
        input = """
// Broken.java
class Broken
{
  /**
   * Detected.
   */
  Object anonymousInnerOK = new Runnable() { public void run () {} };

  /**
   * Detected.
   */
  class InnerOK { InnerOK () { } }

  {
    System.out.println("a number: " + 1000.0 + "");
  }

  /**
   * Not detected.
   */
  Object anonymousInnerBAD = new Runnable() { public void run () {} };

  /**
   * Not detected.
   */
  class InnerBAD { InnerBAD () { } }
}
"""

        expect = ['Broken$1', 'Broken$InnerOK', 'Broken$2', 'Broken$InnerBAD', 'Broken']

        pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.4')
        assert expect == classes, (expect, classes)

        pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.5')
        assert expect == classes, (expect, classes)


    def test_genercis(self):
        """Test that generics don't interfere with detecting anonymous classes"""

        input = """\
import java.util.Date;
import java.util.Comparator;

public class Foo
{
  public void foo()
  {
    Comparator<Date> comp = new Comparator<Date>()
      {
        static final long serialVersionUID = 1L;
        public int compare(Date lhs, Date rhs)
        {
          return 0;
        }
      };
  }
}
"""

        expect = [ 'Foo$1', 'Foo' ]

        pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.6')
        assert expect == classes, (expect, classes)

        pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '6')
        assert expect == classes, (expect, classes)


    def test_in_function_class_declaration(self):
        """
        Test that implementing a class in a function call doesn't confuse SCons.
        """

        input = """
package com.Matthew;

public class AnonDemo {

    public static void main(String[] args) {
        new AnonDemo().execute();
    }

    public void execute() {
        Foo bar = new Foo(new Foo() {
            @Override
            public int getX() { return this.x; }
        }) {
            @Override
            public int getX() { return this.x; }
        };
    }

    public abstract class Foo {
        public int x;
        public abstract int getX();

        public Foo(Foo f) {
            this.x = f.x;
        }

        public Foo() {}
    }
}
"""
        expect = ['AnonDemo$1',
                  'AnonDemo$2',
                  'AnonDemo$Foo',
                  'AnonDemo']
        pkg_dir, classes = SCons.Tool.JavaCommon.parse_java(input, '1.8')
        assert expect == classes, (expect, classes)

    def test_jdk_globs(self):
        """
        Verify that the java path globs work with specific examples.
        :return:
        """
        from SCons.Tool.JavaCommon import (
            java_linux_include_dirs_glob,
            java_linux_version_include_dirs_glob,
            java_win32_dir_glob,
            java_win32_version_dir_glob,
            java_macos_include_dir_glob,
            java_macos_version_include_dir_glob,
        )

        # Test windows globs
        win_java_dirs = [
            (r'C:/Program Files/Java/jdk1.8.0_201/bin', '1.8.0'),
            (r'C:/Program Files/Java/jdk-11.0.2/bin', '11.0.2'),
            (r'C:/Program Files/AdoptOpenJDK/jdk-16.0.1.9-hotspot/bin', '16.0.1'),
            (r'C:/Program Files/Microsoft/jdk-17.0.0.35-hotspot/bin', '17.0.0'),
            (r'C:/Program Files/OpenJDK/openjdk-11.0.13_8/bin', '11.0.13'),
            (r'C:/Program Files/RedHat/java-1.8.0-openjdk-1.8.0.312-1/bin', '1.8.0'),
            (r'C:/Program Files/RedHat/java-11-openjdk-11.0.13-1/bin', '11.0.13'),
        ]

        for (wjd, version) in win_java_dirs:
            if not fnmatch.fnmatch(wjd, java_win32_dir_glob):
                self.fail(
                    "Didn't properly match %s with pattern %s"
                    % (wjd, java_win32_dir_glob)
                )
            if not fnmatch.fnmatch(wjd, java_win32_version_dir_glob % version):
                self.fail(
                    "Didn't properly match %s with version (%s) specific pattern %s"
                    % (wjd, version, java_win32_version_dir_glob % version)
                )

        non_win_java_include_dirs = [
            (
                '/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.191.b12-0.el7_5.x86_64/include',
                '1.8.0',
            ),
            ('/usr/lib/jvm/java-1.8.0-openjdk-amd64/include', '1.8.0'),
            ('/usr/lib/jvm/java-8-openjdk-amd64/include', '8'),
        ]

        # Test non-windows/non-macos globs
        for (wjd, version) in non_win_java_include_dirs:
            match = False
            globs_tried = []
            for jlig in java_linux_include_dirs_glob:
                globs_tried.append(jlig)

                if fnmatch.fnmatch(wjd, jlig):
                    match = True
                    break

            if not match:
                self.fail(
                    "Didn't properly match %s with pattern %s" % (wjd, globs_tried)
                )

            match = False
            globs_tried = []
            for jlvig in java_linux_version_include_dirs_glob:
                globs_tried.append(jlvig % version)
                if fnmatch.fnmatch(wjd, jlvig % version):
                    match = True
                    break

            if not match:
                self.fail(
                    "Didn't properly match %s with version (%s) specific pattern %s"
                    % (wjd, version, globs_tried)
                )

        # Test macos globs
        # Test windows globs
        macos_java_dirs = [
            # ('/System/Library/Frameworks/JavaVM.framework/Headers/', None),
            (
                '/System/Library/Frameworks/JavaVM.framework/Versions/11.0.2/Headers/',
                '11.0.2',
            ),
        ]

        if not fnmatch.fnmatch(
            '/System/Library/Frameworks/JavaVM.framework/Headers/',
            java_macos_include_dir_glob,
        ):
            self.fail(
                "Didn't properly match %s with pattern %s"
                % (
                    '/System/Library/Frameworks/JavaVM.framework/Headers/',
                    java_macos_include_dir_glob,
                )
            )

        for (wjd, version) in macos_java_dirs:
            if not fnmatch.fnmatch(wjd, java_macos_version_include_dir_glob % version):
                self.fail(
                    "Didn't properly match %s with version (%s) specific pattern %s"
                    % (wjd, version, java_macos_version_include_dir_glob % version)
                )


if __name__ == "__main__":
    unittest.main()

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/PharLapCommon.py

"""SCons.Tool.PharLapCommon

This module contains common code used by all Tools for the
Phar Lap ETS tool chain.  Right now, this is linkloc and
386asm.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import os
import os.path
import SCons.Errors
import SCons.Util
import re

def getPharLapPath():
    """Reads the registry to find the installed path of the Phar Lap ETS
    development kit.

    Raises UserError if no installed version of Phar Lap can
    be found."""

    if not SCons.Util.can_read_reg:
        raise SCons.Errors.InternalError("No Windows registry module was found")
    try:
        k=SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE,
                                  'SOFTWARE\\Pharlap\\ETS')
        val, type = SCons.Util.RegQueryValueEx(k, 'BaseDir')

        # The following is a hack...there is (not surprisingly)
        # an odd issue in the Phar Lap plug in that inserts
        # a bunch of junk data after the phar lap path in the
        # registry.  We must trim it.
        idx=val.find('\0')
        if idx >= 0:
            val = val[:idx]
                    
        return os.path.normpath(val)
    except SCons.Util.RegError:
        raise SCons.Errors.UserError("Cannot find Phar Lap ETS path in the registry.  Is it installed properly?")

REGEX_ETS_VER = re.compile(r'#define\s+ETS_VER\s+([0-9]+)')

def getPharLapVersion():
    """Returns the version of the installed ETS Tool Suite as a
    decimal number.  This version comes from the ETS_VER #define in
    the embkern.h header.  For example, '#define ETS_VER 1010' (which
    is what Phar Lap 10.1 defines) would cause this method to return
    1010. Phar Lap 9.1 does not have such a #define, but this method
    will return 910 as a default.

    Raises UserError if no installed version of Phar Lap can
    be found."""

    include_path = os.path.join(getPharLapPath(), os.path.normpath("include/embkern.h"))
    if not os.path.exists(include_path):
        raise SCons.Errors.UserError("Cannot find embkern.h in ETS include directory.\nIs Phar Lap ETS installed properly?")
    with open(include_path, 'r') as f:
        mo = REGEX_ETS_VER.search(f.read())
    if mo:
        return int(mo.group(1))
    # Default return for Phar Lap 9.1
    return 910

def addPharLapPaths(env):
    """This function adds the path to the Phar Lap binaries, includes,
    and libraries, if they are not already there."""
    ph_path = getPharLapPath()

    try:
        env_dict = env['ENV']
    except KeyError:
        env_dict = {}
        env['ENV'] = env_dict
    SCons.Util.AddPathIfNotExists(env_dict, 'PATH',
                                  os.path.join(ph_path, 'bin'))
    SCons.Util.AddPathIfNotExists(env_dict, 'INCLUDE',
                                  os.path.join(ph_path, 'include'))
    SCons.Util.AddPathIfNotExists(env_dict, 'LIB',
                                  os.path.join(ph_path, 'lib'))
    SCons.Util.AddPathIfNotExists(env_dict, 'LIB',
                                  os.path.join(ph_path, os.path.normpath('lib/vclib')))
    
    env['PHARLAP_PATH'] = getPharLapPath()
    env['PHARLAP_VERSION'] = str(getPharLapVersion())
    

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/ToolTests.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

import os
import unittest

import TestUnit

import SCons.Errors
import SCons.Tool


class DummyEnvironment:
    def __init__(self):
        self.dict = {}
    def Detect(self, progs):
        if not SCons.Util.is_List(progs):
            progs = [ progs ]
        return progs[0]
    def Append(self, **kw):
        self.dict.update(kw)
    def __getitem__(self, key):
        return self.dict[key]
    def __setitem__(self, key, val):
        self.dict[key] = val
    def __contains__(self, key):
        return key in self.dict
    def subst(self, string, *args, **kwargs):
        return string

    PHONY_PATH = "/usr/phony/bin"
    def WhereIs(self, key_program):
        # for pathfind test for Issue #3336:
        # need to fake the case where extra paths are searched, and
        # if one has a "hit" after some fails, the fails are left in
        # the environment's PATH. So construct a positive answer if
        # we see a magic known path component in PATH; answer in
        # the negative otherwise.
        paths = self['ENV']['PATH']
        if self.PHONY_PATH in paths:
            return os.path.join(self.PHONY_PATH, key_program)
        return None
    def AppendENVPath(self, pathvar, path):
        # signature matches how called from find_program_path()
        self['ENV'][pathvar] = self['ENV'][pathvar] + os.pathsep + path


class ToolTestCase(unittest.TestCase):
    def test_Tool(self):
        """Test the Tool() function"""

        env = DummyEnvironment()
        env['BUILDERS'] = {}
        env['ENV'] = {}
        env['PLATFORM'] = 'test'
        t = SCons.Tool.Tool('g++')
        t(env)
        assert (env['CXX'] == 'c++' or env['CXX'] == 'g++'), env['CXX']
        assert env['INCPREFIX'] == '-I', env['INCPREFIX']
        assert env['TOOLS'] == ['g++'], env['TOOLS']

        exc_caught = None
        try:
            SCons.Tool.Tool()
        except TypeError:
            exc_caught = 1
        assert exc_caught, "did not catch expected UserError"

        exc_caught = None
        try:
            p = SCons.Tool.Tool('_does_not_exist_')
        except SCons.Errors.UserError as e:
            exc_caught = 1
            # Old msg was Python-style "No tool named", check for new msg:
            assert "No tool module" in str(e), e
        assert exc_caught, "did not catch expected UserError"


    def test_pathfind(self):
        """Test that find_program_path() alters PATH only if add_path is true"""

        env = DummyEnvironment()
        PHONY_PATHS = [
            r'C:\cygwin64\bin',
            r'C:\cygwin\bin',
            '/usr/local/dummy/bin',
            env.PHONY_PATH,  # will be recognized by dummy WhereIs
        ]
        env['ENV'] = {}
        env['ENV']['PATH'] = '/usr/local/bin:/opt/bin:/bin:/usr/bin'
        pre_path = env['ENV']['PATH']
        _ = SCons.Tool.find_program_path(env, 'no_tool', default_paths=PHONY_PATHS)
        assert env['ENV']['PATH'] == pre_path, env['ENV']['PATH']

        _ = SCons.Tool.find_program_path(env, 'no_tool', default_paths=PHONY_PATHS, add_path=True)
        assert env.PHONY_PATH in env['ENV']['PATH'], env['ENV']['PATH']


if __name__ == "__main__":
    loader = unittest.TestLoader()
    loader.testMethodPrefix = 'test_'
    suite = loader.loadTestsFromTestCase(ToolTestCase)
    TestUnit.run(suite)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/__init__.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""SCons tool selection.

Looks for modules that define a callable object that can modify a
construction environment as appropriate for a given tool (or tool chain).

Note that because this subsystem just *selects* a callable that can
modify a construction environment, it's possible for people to define
their own "tool specification" in an arbitrary callable function.  No
one needs to use or tie in to this subsystem in order to roll their own
tool specifications.
"""

import sys
import os
import importlib.util

import SCons.Builder
import SCons.Errors
import SCons.Node.FS
import SCons.Scanner
import SCons.Scanner.C
import SCons.Scanner.D
import SCons.Scanner.Java
import SCons.Scanner.LaTeX
import SCons.Scanner.Prog
import SCons.Scanner.SWIG
from SCons.Tool.linkCommon import LibSymlinksActionFunction, LibSymlinksStrFun

DefaultToolpath = []

CScanner = SCons.Scanner.C.CScanner()
DScanner = SCons.Scanner.D.DScanner()
JavaScanner = SCons.Scanner.Java.JavaScanner()
LaTeXScanner = SCons.Scanner.LaTeX.LaTeXScanner()
PDFLaTeXScanner = SCons.Scanner.LaTeX.PDFLaTeXScanner()
ProgramScanner = SCons.Scanner.Prog.ProgramScanner()
SourceFileScanner = SCons.Scanner.ScannerBase({}, name='SourceFileScanner')
SWIGScanner = SCons.Scanner.SWIG.SWIGScanner()

CSuffixes = [".c", ".C", ".cxx", ".cpp", ".c++", ".cc",
             ".h", ".H", ".hxx", ".hpp", ".hh",
             ".F", ".fpp", ".FPP",
             ".m", ".mm",
             ".S", ".spp", ".SPP", ".sx"]

DSuffixes = ['.d']

IDLSuffixes = [".idl", ".IDL"]

LaTeXSuffixes = [".tex", ".ltx", ".latex"]

SWIGSuffixes = ['.i']

for suffix in CSuffixes:
    SourceFileScanner.add_scanner(suffix, CScanner)

for suffix in DSuffixes:
    SourceFileScanner.add_scanner(suffix, DScanner)

for suffix in SWIGSuffixes:
    SourceFileScanner.add_scanner(suffix, SWIGScanner)

# FIXME: what should be done here? Two scanners scan the same extensions,
# but look for different files, e.g., "picture.eps" vs. "picture.pdf".
# The builders for DVI and PDF explicitly reference their scanners
# I think that means this is not needed???
for suffix in LaTeXSuffixes:
    SourceFileScanner.add_scanner(suffix, LaTeXScanner)
    SourceFileScanner.add_scanner(suffix, PDFLaTeXScanner)

# Tool aliases are needed for those tools whose module names also
# occur in the python standard library (This causes module shadowing and
# can break using python library functions under python3)  or if the current tool/file names
# are not legal module names (violate python's identifier rules or are
# python language keywords).
TOOL_ALIASES = {
    'gettext': 'gettext_tool',
    'clang++': 'clangxx',
    'as': 'asm',
}


class Tool:
    def __init__(self, name, toolpath=None, **kwargs):
        if toolpath is None:
            toolpath = []

        # Rename if there's a TOOL_ALIAS for this tool
        self.name = TOOL_ALIASES.get(name, name)
        self.toolpath = toolpath + DefaultToolpath
        # remember these so we can merge them into the call
        self.init_kw = kwargs

        module = self._tool_module()
        self.generate = module.generate
        self.exists = module.exists
        if hasattr(module, 'options'):
            self.options = module.options

    def _load_dotted_module_py2(self, short_name, full_name, searchpaths=None):
        import imp

        splitname = short_name.split('.')
        index = 0
        srchpths = searchpaths
        for item in splitname:
            file, path, desc = imp.find_module(item, srchpths)
            mod = imp.load_module(full_name, file, path, desc)
            srchpths = [path]
        return mod, file

    def _tool_module(self):
        oldpythonpath = sys.path
        sys.path = self.toolpath + sys.path
        # sys.stderr.write("Tool:%s\nPATH:%s\n"%(self.name,sys.path))

        # From: http://stackoverflow.com/questions/67631/how-to-import-a-module-given-the-full-path/67692#67692
        # import importlib.util
        # spec = importlib.util.spec_from_file_location("module.name", "/path/to/file.py")
        # foo = importlib.util.module_from_spec(spec)
        # spec.loader.exec_module(foo)
        # foo.MyClass()
        # Py 3 code


        # sys.stderr.write("toolpath:%s\n" % self.toolpath)
        # sys.stderr.write("SCONS.TOOL path:%s\n" % sys.modules['SCons.Tool'].__path__)
        debug = False
        spec = None
        found_name = self.name
        add_to_scons_tools_namespace = False
        for path in self.toolpath:
            sepname = self.name.replace('.', os.path.sep)
            file_path = os.path.join(path, "%s.py" % sepname)
            file_package = os.path.join(path, sepname)

            if debug: sys.stderr.write("Trying:%s %s\n" % (file_path, file_package))

            if os.path.isfile(file_path):
                spec = importlib.util.spec_from_file_location(self.name, file_path)
                if debug: print("file_Path:%s FOUND" % file_path)
                break
            elif os.path.isdir(file_package):
                file_package = os.path.join(file_package, '__init__.py')
                spec = importlib.util.spec_from_file_location(self.name, file_package)
                if debug: print("PACKAGE:%s Found" % file_package)
                break

            else:
                continue

        if spec is None:
            if debug: sys.stderr.write("NO SPEC :%s\n" % self.name)
            spec = importlib.util.find_spec("." + self.name, package='SCons.Tool')
            if spec:
                found_name = 'SCons.Tool.' + self.name
                add_to_scons_tools_namespace = True
            if debug: sys.stderr.write("Spec Found? .%s :%s\n" % (self.name, spec))

        if spec is None:
            sconstools = os.path.normpath(sys.modules['SCons.Tool'].__path__[0])
            if self.toolpath:
                sconstools = ", ".join(self.toolpath) + ", " + sconstools
            error_string = "No tool module '%s' found in %s" % (self.name, sconstools)
            raise SCons.Errors.UserError(error_string)

        module = importlib.util.module_from_spec(spec)
        if module is None:
            if debug: print("MODULE IS NONE:%s" % self.name)
            error_string = "Tool module '%s' failed import" % self.name
            raise SCons.Errors.SConsEnvironmentError(error_string)

        # Don't reload a tool we already loaded.
        sys_modules_value = sys.modules.get(found_name, False)

        found_module = None
        if sys_modules_value and sys_modules_value.__file__ == spec.origin:
            found_module = sys.modules[found_name]
        else:
            # Not sure what to do in the case that there already
            # exists sys.modules[self.name] but the source file is
            # different.. ?
            module = spec.loader.load_module(spec.name)

            sys.modules[found_name] = module
            if add_to_scons_tools_namespace:
                # If we found it in SCons.Tool, then add it to the module
                setattr(SCons.Tool, self.name, module)

            found_module = module

        if found_module is not None:
            sys.path = oldpythonpath
            return found_module

        sys.path = oldpythonpath

        full_name = 'SCons.Tool.' + self.name
        try:
            return sys.modules[full_name]
        except KeyError:
            try:
                smpath = sys.modules['SCons.Tool'].__path__
                try:
                    module, file = self._load_dotted_module_py2(self.name, full_name, smpath)
                    setattr(SCons.Tool, self.name, module)
                    if file:
                        file.close()
                    return module
                except ImportError as e:
                    if str(e) != "No module named %s" % self.name:
                        raise SCons.Errors.SConsEnvironmentError(e)
                    try:
                        import zipimport
                        importer = zipimport.zipimporter(sys.modules['SCons.Tool'].__path__[0])
                        module = importer.load_module(full_name)
                        setattr(SCons.Tool, self.name, module)
                        return module
                    except ImportError as e:
                        m = "No tool named '%s': %s" % (self.name, e)
                        raise SCons.Errors.SConsEnvironmentError(m)
            except ImportError as e:
                m = "No tool named '%s': %s" % (self.name, e)
                raise SCons.Errors.SConsEnvironmentError(m)

    def __call__(self, env, *args, **kw):
        if self.init_kw is not None:
            # Merge call kws into init kws;
            # but don't bash self.init_kw.
            if kw is not None:
                call_kw = kw
                kw = self.init_kw.copy()
                kw.update(call_kw)
            else:
                kw = self.init_kw
        env.Append(TOOLS=[self.name])
        if hasattr(self, 'options'):
            import SCons.Variables
            if 'options' not in env:
                from SCons.Script import ARGUMENTS
                env['options'] = SCons.Variables.Variables(args=ARGUMENTS)
            opts = env['options']

            self.options(opts)
            opts.Update(env)

        self.generate(env, *args, **kw)

    def __str__(self):
        return self.name


LibSymlinksAction = SCons.Action.Action(LibSymlinksActionFunction, LibSymlinksStrFun)


##########################################################################
#  Create common executable program / library / object builders

def createProgBuilder(env):
    """This is a utility function that creates the Program
    Builder in an Environment if it is not there already.

    If it is already there, we return the existing one.
    """

    try:
        program = env['BUILDERS']['Program']
    except KeyError:
        import SCons.Defaults
        program = SCons.Builder.Builder(action=SCons.Defaults.LinkAction,
                                        emitter='$PROGEMITTER',
                                        prefix='$PROGPREFIX',
                                        suffix='$PROGSUFFIX',
                                        src_suffix='$OBJSUFFIX',
                                        src_builder='Object',
                                        target_scanner=ProgramScanner)
        env['BUILDERS']['Program'] = program

    return program


def createStaticLibBuilder(env):
    """This is a utility function that creates the StaticLibrary
    Builder in an Environment if it is not there already.

    If it is already there, we return the existing one.
    """

    try:
        static_lib = env['BUILDERS']['StaticLibrary']
    except KeyError:
        action_list = [SCons.Action.Action("$ARCOM", "$ARCOMSTR")]
        if env.get('RANLIB', False) or env.Detect('ranlib'):
            ranlib_action = SCons.Action.Action("$RANLIBCOM", "$RANLIBCOMSTR")
            action_list.append(ranlib_action)

        static_lib = SCons.Builder.Builder(action=action_list,
                                           emitter='$LIBEMITTER',
                                           prefix='$LIBPREFIX',
                                           suffix='$LIBSUFFIX',
                                           src_suffix='$OBJSUFFIX',
                                           src_builder='StaticObject')
        env['BUILDERS']['StaticLibrary'] = static_lib
        env['BUILDERS']['Library'] = static_lib

    return static_lib


def createSharedLibBuilder(env, shlib_suffix='$_SHLIBSUFFIX'):
    """This is a utility function that creates the SharedLibrary
    Builder in an Environment if it is not there already.

    If it is already there, we return the existing one.

    Args:
        shlib_suffix: The suffix specified for the shared library builder

    """

    try:
        shared_lib = env['BUILDERS']['SharedLibrary']
    except KeyError:
        import SCons.Defaults
        action_list = [SCons.Defaults.SharedCheck,
                       SCons.Defaults.ShLinkAction,
                       LibSymlinksAction]
        shared_lib = SCons.Builder.Builder(action=action_list,
                                           emitter="$SHLIBEMITTER",
                                           prefix="$SHLIBPREFIX",
                                           suffix=shlib_suffix,
                                           target_scanner=ProgramScanner,
                                           src_suffix='$SHOBJSUFFIX',
                                           src_builder='SharedObject')
        env['BUILDERS']['SharedLibrary'] = shared_lib

    return shared_lib


def createLoadableModuleBuilder(env, loadable_module_suffix='$_LDMODULESUFFIX'):
    """This is a utility function that creates the LoadableModule
    Builder in an Environment if it is not there already.

    If it is already there, we return the existing one.

    Args:
        loadable_module_suffix: The suffix specified for the loadable module builder

    """

    try:
        ld_module = env['BUILDERS']['LoadableModule']
    except KeyError:
        import SCons.Defaults
        action_list = [SCons.Defaults.SharedCheck,
                       SCons.Defaults.LdModuleLinkAction,
                       LibSymlinksAction]
        ld_module = SCons.Builder.Builder(action=action_list,
                                          emitter="$LDMODULEEMITTER",
                                          prefix="$LDMODULEPREFIX",
                                          suffix=loadable_module_suffix,
                                          target_scanner=ProgramScanner,
                                          src_suffix='$SHOBJSUFFIX',
                                          src_builder='SharedObject')
        env['BUILDERS']['LoadableModule'] = ld_module

    return ld_module


def createObjBuilders(env):
    """This is a utility function that creates the StaticObject
    and SharedObject Builders in an Environment if they
    are not there already.

    If they are there already, we return the existing ones.

    This is a separate function because soooo many Tools
    use this functionality.

    The return is a 2-tuple of (StaticObject, SharedObject)
    """

    try:
        static_obj = env['BUILDERS']['StaticObject']
    except KeyError:
        static_obj = SCons.Builder.Builder(action={},
                                           emitter={},
                                           prefix='$OBJPREFIX',
                                           suffix='$OBJSUFFIX',
                                           src_builder=['CFile', 'CXXFile'],
                                           source_scanner=SourceFileScanner,
                                           single_source=1)
        env['BUILDERS']['StaticObject'] = static_obj
        env['BUILDERS']['Object'] = static_obj

    try:
        shared_obj = env['BUILDERS']['SharedObject']
    except KeyError:
        shared_obj = SCons.Builder.Builder(action={},
                                           emitter={},
                                           prefix='$SHOBJPREFIX',
                                           suffix='$SHOBJSUFFIX',
                                           src_builder=['CFile', 'CXXFile'],
                                           source_scanner=SourceFileScanner,
                                           single_source=1)
        env['BUILDERS']['SharedObject'] = shared_obj

    return (static_obj, shared_obj)


def createCFileBuilders(env):
    """This is a utility function that creates the CFile/CXXFile
    Builders in an Environment if they
    are not there already.

    If they are there already, we return the existing ones.

    This is a separate function because soooo many Tools
    use this functionality.

    The return is a 2-tuple of (CFile, CXXFile)
    """

    try:
        c_file = env['BUILDERS']['CFile']
    except KeyError:
        c_file = SCons.Builder.Builder(action={},
                                       emitter={},
                                       suffix={None: '$CFILESUFFIX'})
        env['BUILDERS']['CFile'] = c_file

        env.SetDefault(CFILESUFFIX='.c')

    try:
        cxx_file = env['BUILDERS']['CXXFile']
    except KeyError:
        cxx_file = SCons.Builder.Builder(action={},
                                         emitter={},
                                         suffix={None: '$CXXFILESUFFIX'})
        env['BUILDERS']['CXXFile'] = cxx_file
        env.SetDefault(CXXFILESUFFIX='.cc')

    return (c_file, cxx_file)


##########################################################################
#  Create common Java builders

def CreateJarBuilder(env):
    """The Jar builder expects a list of class files
    which it can package into a jar file.

    The jar tool provides an interface for passing other types
    of java files such as .java, directories or swig interfaces
    and will build them to class files in which it can package
    into the jar.
    """
    try:
        java_jar = env['BUILDERS']['JarFile']
    except KeyError:
        fs = SCons.Node.FS.get_default_fs()
        jar_com = SCons.Action.Action('$JARCOM', '$JARCOMSTR')
        java_jar = SCons.Builder.Builder(action=jar_com,
                                         suffix='$JARSUFFIX',
                                         src_suffix='$JAVACLASSSUFFIX',
                                         src_builder='JavaClassFile',
                                         source_factory=fs.Entry)
        env['BUILDERS']['JarFile'] = java_jar
    return java_jar


def CreateJavaHBuilder(env):
    try:
        java_javah = env['BUILDERS']['JavaH']
    except KeyError:
        fs = SCons.Node.FS.get_default_fs()
        java_javah_com = SCons.Action.Action('$JAVAHCOM', '$JAVAHCOMSTR')
        java_javah = SCons.Builder.Builder(action=java_javah_com,
                                           src_suffix='$JAVACLASSSUFFIX',
                                           target_factory=fs.Entry,
                                           source_factory=fs.File,
                                           src_builder='JavaClassFile')
        env['BUILDERS']['JavaH'] = java_javah
    return java_javah


def CreateJavaClassFileBuilder(env):
    try:
        java_class_file = env['BUILDERS']['JavaClassFile']
    except KeyError:
        fs = SCons.Node.FS.get_default_fs()
        javac_com = SCons.Action.Action('$JAVACCOM', '$JAVACCOMSTR')
        java_class_file = SCons.Builder.Builder(action=javac_com,
                                                emitter={},
                                                # suffix = '$JAVACLASSSUFFIX',
                                                src_suffix='$JAVASUFFIX',
                                                src_builder=['JavaFile'],
                                                target_factory=fs.Entry,
                                                source_factory=fs.File,
                                                target_scanner=JavaScanner)
        env['BUILDERS']['JavaClassFile'] = java_class_file
    return java_class_file


def CreateJavaClassDirBuilder(env):
    try:
        java_class_dir = env['BUILDERS']['JavaClassDir']
    except KeyError:
        fs = SCons.Node.FS.get_default_fs()
        javac_com = SCons.Action.Action('$JAVACCOM', '$JAVACCOMSTR')
        java_class_dir = SCons.Builder.Builder(action=javac_com,
                                               emitter={},
                                               target_factory=fs.Dir,
                                               source_factory=fs.Dir,
                                               target_scanner=JavaScanner)
        env['BUILDERS']['JavaClassDir'] = java_class_dir
    return java_class_dir


def CreateJavaFileBuilder(env):
    try:
        java_file = env['BUILDERS']['JavaFile']
    except KeyError:
        java_file = SCons.Builder.Builder(action={},
                                          emitter={},
                                          suffix={None: '$JAVASUFFIX'})
        env['BUILDERS']['JavaFile'] = java_file
        env['JAVASUFFIX'] = '.java'
    return java_file


class ToolInitializerMethod:
    """
    This is added to a construction environment in place of a
    method(s) normally called for a Builder (env.Object, env.StaticObject,
    etc.).  When called, it has its associated ToolInitializer
    object search the specified list of tools and apply the first
    one that exists to the construction environment.  It then calls
    whatever builder was (presumably) added to the construction
    environment in place of this particular instance.
    """

    def __init__(self, name, initializer):
        """
        Note:  we store the tool name as __name__ so it can be used by
        the class that attaches this to a construction environment.
        """
        self.__name__ = name
        self.initializer = initializer

    def get_builder(self, env):
        """
        Returns the appropriate real Builder for this method name
        after having the associated ToolInitializer object apply
        the appropriate Tool module.
        """
        builder = getattr(env, self.__name__)

        self.initializer.apply_tools(env)

        builder = getattr(env, self.__name__)
        if builder is self:
            # There was no Builder added, which means no valid Tool
            # for this name was found (or possibly there's a mismatch
            # between the name we were called by and the Builder name
            # added by the Tool module).
            return None

        self.initializer.remove_methods(env)

        return builder

    def __call__(self, env, *args, **kw):
        """
        """
        builder = self.get_builder(env)
        if builder is None:
            return [], []
        return builder(*args, **kw)


class ToolInitializer:
    """
    A class for delayed initialization of Tools modules.

    Instances of this class associate a list of Tool modules with
    a list of Builder method names that will be added by those Tool
    modules.  As part of instantiating this object for a particular
    construction environment, we also add the appropriate
    ToolInitializerMethod objects for the various Builder methods
    that we want to use to delay Tool searches until necessary.
    """

    def __init__(self, env, tools, names):
        if not SCons.Util.is_List(tools):
            tools = [tools]
        if not SCons.Util.is_List(names):
            names = [names]
        self.env = env
        self.tools = tools
        self.names = names
        self.methods = {}
        for name in names:
            method = ToolInitializerMethod(name, self)
            self.methods[name] = method
            env.AddMethod(method)

    def remove_methods(self, env):
        """
        Removes the methods that were added by the tool initialization
        so we no longer copy and re-bind them when the construction
        environment gets cloned.
        """
        for method in self.methods.values():
            env.RemoveMethod(method)

    def apply_tools(self, env):
        """
        Searches the list of associated Tool modules for one that
        exists, and applies that to the construction environment.
        """
        for t in self.tools:
            tool = SCons.Tool.Tool(t)
            if tool.exists(env):
                env.Tool(tool)
                return

        # If we fall through here, there was no tool module found.
        # This is where we can put an informative error message
        # about the inability to find the tool.   We'll start doing
        # this as we cut over more pre-defined Builder+Tools to use
        # the ToolInitializer class.


def Initializers(env):
    ToolInitializer(env, ['install'], ['_InternalInstall', '_InternalInstallAs', '_InternalInstallVersionedLib'])

    def Install(self, *args, **kw):
        return self._InternalInstall(*args, **kw)

    def InstallAs(self, *args, **kw):
        return self._InternalInstallAs(*args, **kw)

    def InstallVersionedLib(self, *args, **kw):
        return self._InternalInstallVersionedLib(*args, **kw)

    env.AddMethod(Install)
    env.AddMethod(InstallAs)
    env.AddMethod(InstallVersionedLib)


def FindTool(tools, env):
    for tool in tools:
        t = Tool(tool)
        if t.exists(env):
            return tool
    return None


def FindAllTools(tools, env):
    def ToolExists(tool, env=env):
        return Tool(tool).exists(env)

    return list(filter(ToolExists, tools))


def tool_list(platform, env):
    other_plat_tools = []
    # XXX this logic about what tool to prefer on which platform
    #     should be moved into either the platform files or
    #     the tool files themselves.
    # The search orders here are described in the man page.  If you
    # change these search orders, update the man page as well.
    if str(platform) == 'win32':
        "prefer Microsoft tools on Windows"
        linkers = ['mslink', 'gnulink', 'ilink', 'linkloc', 'ilink32']
        c_compilers = ['msvc', 'mingw', 'gcc', 'intelc', 'icl', 'icc', 'cc', 'bcc32']
        cxx_compilers = ['msvc', 'intelc', 'icc', 'g++', 'cxx', 'bcc32']
        assemblers = ['masm', 'nasm', 'gas', '386asm']
        fortran_compilers = ['gfortran', 'g77', 'ifl', 'cvf', 'f95', 'f90', 'fortran']
        ars = ['mslib', 'ar', 'tlib']
        other_plat_tools = ['msvs', 'midl', 'wix']
    elif str(platform) == 'os2':
        "prefer IBM tools on OS/2"
        linkers = ['ilink', 'gnulink', ]  # 'mslink']
        c_compilers = ['icc', 'gcc', ]  # 'msvc', 'cc']
        cxx_compilers = ['icc', 'g++', ]  # 'msvc', 'cxx']
        assemblers = ['nasm', ]  # 'masm', 'gas']
        fortran_compilers = ['ifl', 'g77']
        ars = ['ar', ]  # 'mslib']
    elif str(platform) == 'irix':
        "prefer MIPSPro on IRIX"
        linkers = ['sgilink', 'gnulink']
        c_compilers = ['sgicc', 'gcc', 'cc']
        cxx_compilers = ['sgicxx', 'g++', 'cxx']
        assemblers = ['as', 'gas']
        fortran_compilers = ['f95', 'f90', 'f77', 'g77', 'fortran']
        ars = ['sgiar']
    elif str(platform) == 'sunos':
        "prefer Forte tools on SunOS"
        linkers = ['sunlink', 'gnulink']
        c_compilers = ['suncc', 'gcc', 'cc']
        cxx_compilers = ['suncxx', 'g++', 'cxx']
        assemblers = ['as', 'gas']
        fortran_compilers = ['sunf95', 'sunf90', 'sunf77', 'f95', 'f90', 'f77',
                             'gfortran', 'g77', 'fortran']
        ars = ['sunar']
    elif str(platform) == 'hpux':
        "prefer aCC tools on HP-UX"
        linkers = ['hplink', 'gnulink']
        c_compilers = ['hpcc', 'gcc', 'cc']
        cxx_compilers = ['hpcxx', 'g++', 'cxx']
        assemblers = ['as', 'gas']
        fortran_compilers = ['f95', 'f90', 'f77', 'g77', 'fortran']
        ars = ['ar']
    elif str(platform) == 'aix':
        "prefer AIX Visual Age tools on AIX"
        linkers = ['aixlink', 'gnulink']
        c_compilers = ['aixcc', 'gcc', 'cc']
        cxx_compilers = ['aixcxx', 'g++', 'cxx']
        assemblers = ['as', 'gas']
        fortran_compilers = ['f95', 'f90', 'aixf77', 'g77', 'fortran']
        ars = ['ar']
    elif str(platform) == 'darwin':
        "prefer GNU tools on Mac OS X, except for some linkers and IBM tools"
        linkers = ['applelink', 'gnulink']
        c_compilers = ['gcc', 'cc']
        cxx_compilers = ['g++', 'cxx']
        assemblers = ['as']
        fortran_compilers = ['gfortran', 'f95', 'f90', 'g77']
        ars = ['ar']
    elif str(platform) == 'cygwin':
        "prefer GNU tools on Cygwin, except for a platform-specific linker"
        linkers = ['cyglink', 'mslink', 'ilink']
        c_compilers = ['gcc', 'msvc', 'intelc', 'icc', 'cc']
        cxx_compilers = ['g++', 'msvc', 'intelc', 'icc', 'cxx']
        assemblers = ['gas', 'nasm', 'masm']
        fortran_compilers = ['gfortran', 'g77', 'ifort', 'ifl', 'f95', 'f90', 'f77']
        ars = ['ar', 'mslib']
    else:
        "prefer GNU tools on all other platforms"
        linkers = ['gnulink', 'ilink']
        c_compilers = ['gcc', 'intelc', 'icc', 'cc']
        cxx_compilers = ['g++', 'intelc', 'icc', 'cxx']
        assemblers = ['gas', 'nasm', 'masm']
        fortran_compilers = ['gfortran', 'g77', 'ifort', 'ifl', 'f95', 'f90', 'f77']
        ars = ['ar', ]

    if not str(platform) == 'win32':
        other_plat_tools += ['m4', 'rpm']

    c_compiler = FindTool(c_compilers, env) or c_compilers[0]

    # XXX this logic about what tool provides what should somehow be
    #     moved into the tool files themselves.
    if c_compiler and c_compiler == 'mingw':
        # MinGW contains a linker, C compiler, C++ compiler,
        # Fortran compiler, archiver and assembler:
        cxx_compiler = None
        linker = None
        assembler = None
        fortran_compiler = None
        ar = None
    else:
        # Don't use g++ if the C compiler has built-in C++ support:
        if c_compiler in ('msvc', 'intelc', 'icc'):
            cxx_compiler = None
        else:
            cxx_compiler = FindTool(cxx_compilers, env) or cxx_compilers[0]
        linker = FindTool(linkers, env) or linkers[0]
        assembler = FindTool(assemblers, env) or assemblers[0]
        fortran_compiler = FindTool(fortran_compilers, env) or fortran_compilers[0]
        ar = FindTool(ars, env) or ars[0]

    d_compilers = ['dmd', 'ldc', 'gdc']
    d_compiler = FindTool(d_compilers, env) or d_compilers[0]

    other_tools = FindAllTools(other_plat_tools + [
        # TODO: merge 'install' into 'filesystem' and
        # make 'filesystem' the default
        'filesystem',
        # Parser generators
        'lex', 'yacc',
        # Foreign function interface
        'rpcgen', 'swig',
        # Java
        'jar', 'javac', 'javah', 'rmic',
        # TeX
        'dvipdf', 'dvips', 'gs',
        'tex', 'latex', 'pdflatex', 'pdftex',
        # Archivers
        'tar', 'zip',
        # File builders (text)
        'textfile',
    ], env)

    tools = [
        linker,
        c_compiler,
        cxx_compiler,
        fortran_compiler,
        assembler,
        ar,
        d_compiler,
    ] + other_tools

    return [x for x in tools if x]


def find_program_path(env, key_program, default_paths=None, add_path=False) -> str:
    """
    Find the location of a tool using various means.

    Mainly for windows where tools aren't all installed in /usr/bin, etc.

    Args:
        env: Current Construction Environment.
        key_program: Tool to locate.
        default_paths: List of additional paths this tool might be found in.
        add_path: If true, add path found if it was from *default_paths*.
    """
    # First search in the SCons path
    path = env.WhereIs(key_program)
    if path:
        return path

    # Then in the OS path
    path = SCons.Util.WhereIs(key_program)
    if path:
        return path

    # Finally, add the defaults and check again.
    if default_paths is None:
        return path

    save_path = env['ENV']['PATH']
    for p in default_paths:
        env.AppendENVPath('PATH', p)
    path = env.WhereIs(key_program)

    # By default, do not change ['ENV']['PATH'] permananetly
    # leave that to the caller, unless add_path is true.
    env['ENV']['PATH'] = save_path
    if path and add_path:
        bin_dir = os.path.dirname(path)
        env.AppendENVPath('PATH', bin_dir)

    return path

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/aixc++.py

"""SCons.Tool.aixc++

Tool-specific initialization for IBM xlC / Visual Age C++ compiler.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

#forward proxy to the preffered cxx version
from SCons.Tool.aixcxx import *

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/aixcc.py

"""SCons.Tool.aixcc

Tool-specific initialization for IBM xlc / Visual Age C compiler.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import os.path

import SCons.Platform.aix

from . import cc

packages = ['vac.C', 'ibmcxx.cmp']

def get_xlc(env):
    xlc = env.get('CC', 'xlc')
    return SCons.Platform.aix.get_xlc(env, xlc, packages)

def generate(env):
    """Add Builders and construction variables for xlc / Visual Age
    suite to an Environment."""
    path, _cc, version = get_xlc(env)
    if path and _cc:
        _cc = os.path.join(path, _cc)

    if 'CC' not in env:
        env['CC'] = _cc

    cc.generate(env)

    if version:
        env['CCVERSION'] = version

def exists(env):
    path, _cc, version = get_xlc(env)
    if path and _cc:
        xlc = os.path.join(path, _cc)
        if os.path.exists(xlc):
            return xlc
    return None

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/aixcxx.py

"""SCons.Tool.aixc++

Tool-specific initialization for IBM xlC / Visual Age C++ compiler.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import os.path

import SCons.Platform.aix

import SCons.Tool.cxx
cplusplus = SCons.Tool.cxx
#cplusplus = __import__('cxx', globals(), locals(), [])

packages = ['vacpp.cmp.core', 'vacpp.cmp.batch', 'vacpp.cmp.C', 'ibmcxx.cmp']

def get_xlc(env):
    xlc = env.get('CXX', 'xlC')
    return SCons.Platform.aix.get_xlc(env, xlc, packages)

def generate(env):
    """Add Builders and construction variables for xlC / Visual Age
    suite to an Environment."""
    path, _cxx, version = get_xlc(env)
    if path and _cxx:
        _cxx = os.path.join(path, _cxx)

    if 'CXX' not in env:
        env['CXX'] = _cxx

    cplusplus.generate(env)

    if version:
        env['CXXVERSION'] = version
    
def exists(env):
    path, _cxx, version = get_xlc(env)
    if path and _cxx:
        xlc = os.path.join(path, _cxx)
        if os.path.exists(xlc):
            return xlc
    return None

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/aixf77.py

"""SCons.Tool.aixf77

Tool-specific initialization for IBM Visual Age f77 Fortran compiler.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import os.path

#import SCons.Platform.aix

from . import f77

# It would be good to look for the AIX F77 package the same way we're now
# looking for the C and C++ packages.  This should be as easy as supplying
# the correct package names in the following list and uncommenting the
# SCons.Platform.aix_get_xlc() call in the function below.
packages = []

def get_xlf77(env):
    xlf77 = env.get('F77', 'xlf77')
    xlf77_r = env.get('SHF77', 'xlf77_r')
    #return SCons.Platform.aix.get_xlc(env, xlf77, xlf77_r, packages)
    return (None, xlf77, xlf77_r, None)

def generate(env):
    """
    Add Builders and construction variables for the Visual Age FORTRAN
    compiler to an Environment.
    """
    path, _f77, _shf77, version = get_xlf77(env)
    if path:
        _f77 = os.path.join(path, _f77)
        _shf77 = os.path.join(path, _shf77)

    f77.generate(env)

    env['F77'] = _f77
    env['SHF77'] = _shf77

def exists(env):
    path, _f77, _shf77, version = get_xlf77(env)
    if path and _f77:
        xlf77 = os.path.join(path, _f77)
        if os.path.exists(xlf77):
            return xlf77
    return None

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/aixlink.py

"""SCons.Tool.aixlink

Tool-specific initialization for the IBM Visual Age linker.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""

#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

import os
import os.path

import SCons.Util

from . import aixcc
from . import link

import SCons.Tool.cxx
cplusplus = SCons.Tool.cxx


def smart_linkflags(source, target, env, for_signature):
    if cplusplus.iscplusplus(source):
        build_dir = env.subst('$BUILDDIR', target=target, source=source)
        if build_dir:
            return '-qtempinc=' + os.path.join(build_dir, 'tempinc')
    return ''


def generate(env):
    """
    Add Builders and construction variables for Visual Age linker to
    an Environment.
    """
    link.generate(env)

    env['SMARTLINKFLAGS'] = smart_linkflags
    env['LINKFLAGS']      = SCons.Util.CLVar('$SMARTLINKFLAGS')
    env['SHLINKFLAGS']    = SCons.Util.CLVar('$LINKFLAGS -qmkshrobj -qsuppress=1501-218')
    env['SHLIBSUFFIX']    = '.a'


def exists(env):
    # TODO: sync with link.smart_link() to choose a linker
    linkers = { 'CXX': ['aixc++'], 'CC': ['aixcc'] }
    alltools = []
    for langvar, linktools in linkers.items():
        if langvar in env:  # use CC over CXX when user specified CC but not CXX
            return SCons.Tool.FindTool(linktools, env)
        alltools.extend(linktools)
    return SCons.Tool.FindTool(alltools, env)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/applelink.py

"""SCons.Tool.applelink

Tool-specific initialization for Apple's gnu-like linker.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

# Even though the Mac is based on the GNU toolchain, it doesn't understand
# the -rpath option, so we use the "link" tool instead of "gnulink".
from SCons.Util import CLVar
from SCons.Errors import UserError
from . import link

# User programmatically describes how SHLIBVERSION maps to values for compat/current.
_APPLELIB_MAX_VERSION_VALUES = (65535, 255, 255)


class AppleLinkInvalidCurrentVersionException(Exception):
    pass


class AppleLinkInvalidCompatibilityVersionException(Exception):
    pass


def _applelib_check_valid_version(version_string):
    """
    Check that the version # is valid.
    X[.Y[.Z]]
    where X 0-65535
    where Y either not specified or 0-255
    where Z either not specified or 0-255
    :param version_string:
    :return:
    """
    parts = version_string.split('.')
    if len(parts) > 3:
        return False, "Version string has too many periods [%s]" % version_string
    if len(parts) <= 0:
        return False, "Version string unspecified [%s]" % version_string

    for (i, p) in enumerate(parts):
        try:
            p_i = int(p)
        except ValueError:
            return False, "Version component %s (from %s) is not a number" % (p, version_string)
        if p_i < 0 or p_i > _APPLELIB_MAX_VERSION_VALUES[i]:
            return False, "Version component %s (from %s) is not valid value should be between 0 and %d" % (
                p, version_string, _APPLELIB_MAX_VERSION_VALUES[i])

    return True, ""


def _applelib_currentVersionFromSoVersion(source, target, env, for_signature):
    """
    A generator function to create the -Wl,-current_version flag if needed.
    If env['APPLELINK_NO_CURRENT_VERSION'] contains a true value no flag will be generated
    Otherwise if APPLELINK_CURRENT_VERSION is not specified, env['SHLIBVERSION']
    will be used.

    :param source:
    :param target:
    :param env:
    :param for_signature:
    :return: A string providing the flag to specify the current_version of the shared library
    """
    if env.get('APPLELINK_NO_CURRENT_VERSION', False):
        return ""
    elif env.get('APPLELINK_CURRENT_VERSION', False):
        version_string = env['APPLELINK_CURRENT_VERSION']
    elif env.get('SHLIBVERSION', False):
        version_string = env['SHLIBVERSION']
    else:
        return ""

    version_string = ".".join(version_string.split('.')[:3])

    valid, reason = _applelib_check_valid_version(version_string)
    if not valid:
        raise AppleLinkInvalidCurrentVersionException(reason)

    return "-Wl,-current_version,%s" % version_string


def _applelib_compatVersionFromSoVersion(source, target, env, for_signature):
    """
    A generator function to create the -Wl,-compatibility_version flag if needed.
    If env['APPLELINK_NO_COMPATIBILITY_VERSION'] contains a true value no flag will be generated
    Otherwise if APPLELINK_COMPATIBILITY_VERSION is not specified
    the first two parts of env['SHLIBVERSION'] will be used with a .0 appended.

    :param source:
    :param target:
    :param env:
    :param for_signature:
    :return: A string providing the flag to specify the compatibility_version of the shared library
    """
    if env.get('APPLELINK_NO_COMPATIBILITY_VERSION', False):
        return ""
    elif env.get('APPLELINK_COMPATIBILITY_VERSION', False):
        version_string = env['APPLELINK_COMPATIBILITY_VERSION']
    elif env.get('SHLIBVERSION', False):
        version_string = ".".join(env['SHLIBVERSION'].split('.')[:2] + ['0'])
    else:
        return ""

    if version_string is None:
        return ""

    valid, reason = _applelib_check_valid_version(version_string)
    if not valid:
        raise AppleLinkInvalidCompatibilityVersionException(reason)

    return "-Wl,-compatibility_version,%s" % version_string

def _applelib_soname(target, source, env, for_signature):
    """
    Override default _soname() function from SCons.Tools.linkCommon.SharedLibrary.
    Apple's file naming for versioned shared libraries puts the version string before
    the shared library suffix (.dylib), instead of after.
    """
    if "SONAME" in env:
        # Now verify that SOVERSION is not also set as that is not allowed
        if "SOVERSION" in env:
            raise UserError(
                "Ambiguous library .so naming, both SONAME: %s and SOVERSION: %s are defined. "
                "Only one can be defined for a target library."
                % (env["SONAME"], env["SOVERSION"])
            )
        return "$SONAME"
    else:
        return "$SHLIBPREFIX$_get_shlib_stem$_SHLIBSOVERSION${SHLIBSUFFIX}"


def generate(env):
    """Add Builders and construction variables for applelink to an
    Environment."""
    link.generate(env)

    env['FRAMEWORKPATHPREFIX'] = '-F'
    env['_FRAMEWORKPATH'] = '${_concat(FRAMEWORKPATHPREFIX, FRAMEWORKPATH, "", __env__, RDirs)}'

    env['_FRAMEWORKS'] = '${_concat("-framework ", FRAMEWORKS, "", __env__)}'
    env['LINKCOM'] = env['LINKCOM'] + ' $_FRAMEWORKPATH $_FRAMEWORKS $FRAMEWORKSFLAGS'
    env['SHLINKFLAGS'] = CLVar('$LINKFLAGS -dynamiclib')
    env['SHLINKCOM'] = env['SHLINKCOM'] + ' $_FRAMEWORKPATH $_FRAMEWORKS $FRAMEWORKSFLAGS'

    env['_APPLELINK_CURRENT_VERSION'] = _applelib_currentVersionFromSoVersion
    env['_APPLELINK_COMPATIBILITY_VERSION'] = _applelib_compatVersionFromSoVersion
    env['_SHLIBVERSIONFLAGS'] = '$_APPLELINK_CURRENT_VERSION $_APPLELINK_COMPATIBILITY_VERSION '
    env['_LDMODULEVERSIONFLAGS'] = '$_APPLELINK_CURRENT_VERSION $_APPLELINK_COMPATIBILITY_VERSION '

    # override the default for loadable modules, which are different
    # on OS X than dynamic shared libs.  echoing what XCode does for
    # pre/suffixes:
    env['LDMODULEPREFIX'] = ''
    env['LDMODULESUFFIX'] = ''
    env['LDMODULEFLAGS'] = CLVar('$LINKFLAGS -bundle')
    env['LDMODULECOM'] = '$LDMODULE -o ${TARGET} $LDMODULEFLAGS' \
                         ' $SOURCES $_LIBDIRFLAGS $_LIBFLAGS $_FRAMEWORKPATH $_FRAMEWORKS $FRAMEWORKSFLAGS'

    # New stuff
    #
    env['_SHLIBSUFFIX'] = '${_SHLIBVERSION}${SHLIBSUFFIX}'

    env['__SHLIBVERSIONFLAGS'] = '${__lib_either_version_flag(__env__,' \
                                 '"SHLIBVERSION","_APPLELINK_CURRENT_VERSION", "_SHLIBVERSIONFLAGS")}'
    env['__LDMODULEVERSIONFLAGS'] = '${__lib_either_version_flag(__env__,' \
                                    '"LDMODULEVERSION","_APPLELINK_CURRENT_VERSION", "_LDMODULEVERSIONFLAGS")}'

    env["_SHLIBSONAME"] = _applelib_soname


def exists(env):
    return env['PLATFORM'] == 'darwin'

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/ar.py

"""SCons.Tool.ar

Tool-specific initialization for ar (library archive).

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import SCons.Defaults
import SCons.Tool
import SCons.Util


def generate(env):
    """Add Builders and construction variables for ar to an Environment."""
    SCons.Tool.createStaticLibBuilder(env)

    env['AR']          = 'ar'
    env['ARFLAGS']     = SCons.Util.CLVar('rc')
    env['ARCOM']       = '$AR $ARFLAGS $TARGET $SOURCES'
    env['LIBPREFIX']   = 'lib'
    env['LIBSUFFIX']   = '.a'

    if env.get('RANLIB',env.Detect('ranlib')) :
        env['RANLIB']      = env.get('RANLIB','ranlib')
        env['RANLIBFLAGS'] = SCons.Util.CLVar('')
        env['RANLIBCOM']   = '$RANLIB $RANLIBFLAGS $TARGET'

def exists(env):
    return env.Detect('ar')

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/as.py

"""SCons.Tool.as

Tool-specific initialization for generic assembler.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

#
# forward proxy to the preferred asm version
#
import SCons.Tool.asm

# Resolve FLAKE8 F401 (make sider happy)
generate = SCons.Tool.asm.generate
exists = SCons.Tool.asm.exists


# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/asm.py

"""SCons.Tool.as

Tool-specific initialization for as, the generic Posix assembler.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import SCons.Defaults
import SCons.Tool
import SCons.Util

assemblers = ['as']

ASSuffixes = ['.s', '.asm', '.ASM']
ASPPSuffixes = ['.spp', '.SPP', '.sx']
if SCons.Util.case_sensitive_suffixes('.s', '.S'):
    ASPPSuffixes.extend(['.S'])
else:
    ASSuffixes.extend(['.S'])

def generate(env):
    """Add Builders and construction variables for as to an Environment."""
    static_obj, shared_obj = SCons.Tool.createObjBuilders(env)

    for suffix in ASSuffixes:
        static_obj.add_action(suffix, SCons.Defaults.ASAction)
        shared_obj.add_action(suffix, SCons.Defaults.ASAction)
        static_obj.add_emitter(suffix, SCons.Defaults.StaticObjectEmitter)
        shared_obj.add_emitter(suffix, SCons.Defaults.SharedObjectEmitter)

    for suffix in ASPPSuffixes:
        static_obj.add_action(suffix, SCons.Defaults.ASPPAction)
        shared_obj.add_action(suffix, SCons.Defaults.ASPPAction)
        static_obj.add_emitter(suffix, SCons.Defaults.StaticObjectEmitter)
        shared_obj.add_emitter(suffix, SCons.Defaults.SharedObjectEmitter)

    env['AS']        = env.Detect(assemblers) or 'as'
    env['ASFLAGS']   = SCons.Util.CLVar('')
    env['ASCOM']     = '$AS $ASFLAGS -o $TARGET $SOURCES'
    env['ASPPFLAGS'] = '$ASFLAGS'
    env['ASPPCOM']   = '$CC $ASPPFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS -c -o $TARGET $SOURCES'

def exists(env):
    return env.Detect(assemblers)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/bcc32.py

"""SCons.Tool.bcc32

XXX

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import os
import os.path

import SCons.Defaults
import SCons.Tool
import SCons.Util

def findIt(program, env):
    # First search in the SCons path and then the OS path:
    borwin = env.WhereIs(program) or SCons.Util.WhereIs(program)
    if borwin:
        dir = os.path.dirname(borwin)
        env.PrependENVPath('PATH', dir)
    return borwin

def generate(env):
    findIt('bcc32', env)
    """Add Builders and construction variables for bcc to an
    Environment."""
    static_obj, shared_obj = SCons.Tool.createObjBuilders(env)
    for suffix in ['.c', '.cpp']:
        static_obj.add_action(suffix, SCons.Defaults.CAction)
        shared_obj.add_action(suffix, SCons.Defaults.ShCAction)
        static_obj.add_emitter(suffix, SCons.Defaults.StaticObjectEmitter)
        shared_obj.add_emitter(suffix, SCons.Defaults.SharedObjectEmitter)

    env['CC']        = 'bcc32'
    env['CCFLAGS']   = SCons.Util.CLVar('')
    env['CFLAGS']   = SCons.Util.CLVar('')
    env['CCCOM']     = '$CC -q $CFLAGS $CCFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS -c -o$TARGET $SOURCES'
    env['SHCC']      = '$CC'
    env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS')
    env['SHCFLAGS'] = SCons.Util.CLVar('$CFLAGS')
    env['SHCCCOM']   = '$SHCC -WD $SHCFLAGS $SHCCFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS -c -o$TARGET $SOURCES'
    env['CPPDEFPREFIX']  = '-D'
    env['CPPDEFSUFFIX']  = ''
    env['INCPREFIX']  = '-I'
    env['INCSUFFIX']  = ''
    env['SHOBJSUFFIX'] = '.dll'
    env['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 0
    env['CFILESUFFIX'] = '.cpp'

def exists(env):
    return findIt('bcc32', env)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/c++.py

"""SCons.Tool.c++

Tool-specific initialization for generic Posix C++ compilers.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"


#forward proxy to the preffered cxx version
from SCons.Tool.cxx import *


# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/cc.py

"""SCons.Tool.cc

Tool-specific initialization for generic Posix C compilers.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import SCons.Tool
import SCons.Defaults
import SCons.Util

CSuffixes = ['.c', '.m']
if not SCons.Util.case_sensitive_suffixes('.c', '.C'):
    CSuffixes.append('.C')

def add_common_cc_variables(env):
    """
    Add underlying common "C compiler" variables that
    are used by multiple tools (specifically, c++).
    """
    if '_CCCOMCOM' not in env:
        env['_CCCOMCOM'] = '$CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS'
        # It's a hack to test for darwin here, but the alternative
        # of creating an applecc.py to contain this seems overkill.
        # Maybe someday the Apple platform will require more setup and
        # this logic will be moved.
        env['FRAMEWORKS'] = SCons.Util.CLVar('')
        env['FRAMEWORKPATH'] = SCons.Util.CLVar('')
        if env['PLATFORM'] == 'darwin':
            env['_CCCOMCOM'] = env['_CCCOMCOM'] + ' $_FRAMEWORKPATH'

    if 'CCFLAGS' not in env:
        env['CCFLAGS']   = SCons.Util.CLVar('')

    if 'SHCCFLAGS' not in env:
        env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS')

compilers = ['cc']

def generate(env):
    """
    Add Builders and construction variables for C compilers to an Environment.
    """
    static_obj, shared_obj = SCons.Tool.createObjBuilders(env)

    for suffix in CSuffixes:
        static_obj.add_action(suffix, SCons.Defaults.CAction)
        shared_obj.add_action(suffix, SCons.Defaults.ShCAction)
        static_obj.add_emitter(suffix, SCons.Defaults.StaticObjectEmitter)
        shared_obj.add_emitter(suffix, SCons.Defaults.SharedObjectEmitter)

    add_common_cc_variables(env)

    if 'CC' not in env:
        env['CC']    = env.Detect(compilers) or compilers[0]
    env['CFLAGS']    = SCons.Util.CLVar('')
    env['CCCOM']     = '$CC -o $TARGET -c $CFLAGS $CCFLAGS $_CCCOMCOM $SOURCES'
    env['SHCC']      = '$CC'
    env['SHCFLAGS'] = SCons.Util.CLVar('$CFLAGS')
    env['SHCCCOM']   = '$SHCC -o $TARGET -c $SHCFLAGS $SHCCFLAGS $_CCCOMCOM $SOURCES'

    env['CPPDEFPREFIX']  = '-D'
    env['CPPDEFSUFFIX']  = ''
    env['INCPREFIX']  = '-I'
    env['INCSUFFIX']  = ''
    env['SHOBJSUFFIX'] = '.os'
    env['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 0

    env['CFILESUFFIX'] = '.c'

def exists(env):
    return env.Detect(env.get('CC', compilers))

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/clang.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""Tool-specific initialization for clang.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""

# Based on SCons/Tool/gcc.py by Paweł Tomulik 2014 as a separate tool.
# Brought into the SCons mainline by Russel Winder 2017.

import os
import re
import subprocess

import SCons.Util
import SCons.Tool.cc
from SCons.Tool.clangCommon import get_clang_install_dirs
from SCons.Tool.MSCommon import msvc_setup_env_once


compilers = ['clang']


def generate(env):
    """Add Builders and construction variables for clang to an Environment."""
    SCons.Tool.cc.generate(env)

    if env['PLATFORM'] == 'win32':
        # Ensure that we have a proper path for clang
        clang = SCons.Tool.find_program_path(env, compilers[0], 
                                             default_paths=get_clang_install_dirs(env['PLATFORM']))
        if clang:
            clang_bin_dir = os.path.dirname(clang)
            env.AppendENVPath('PATH', clang_bin_dir)

            # Set-up ms tools paths
            msvc_setup_env_once(env)

    env['CC'] = env.Detect(compilers) or 'clang'
    if env['PLATFORM'] in ['cygwin', 'win32']:
        env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS')
    else:
        env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS -fPIC')

    # determine compiler version
    if env['CC']:
        # pipe = SCons.Action._subproc(env, [env['CC'], '-dumpversion'],
        pipe = SCons.Action._subproc(env, [env['CC'], '--version'],
                                     stdin='devnull',
                                     stderr='devnull',
                                     stdout=subprocess.PIPE)
        if pipe.wait() != 0: return
        # clang -dumpversion is of no use
        with pipe.stdout:
            line = pipe.stdout.readline()
        line = line.decode()
        match = re.search(r'clang +version +([0-9]+(?:\.[0-9]+)+)', line)
        if match:
            env['CCVERSION'] = match.group(1)

    env['CCDEPFLAGS'] = '-MMD -MF ${TARGET}.d'
    env["NINJA_DEPFILE_PARSE_FORMAT"] = 'clang'


def exists(env):
    return env.Detect(compilers)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/clangxx.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""Tool-specific initialization for clang++.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""

# Based on SCons/Tool/g++.py by Paweł Tomulik 2014 as a separate tool.
# Brought into the SCons mainline by Russel Winder 2017.

import os.path
import re
import subprocess

import SCons.Tool
import SCons.Util
import SCons.Tool.cxx
from SCons.Tool.clangCommon import get_clang_install_dirs
from SCons.Tool.MSCommon import msvc_setup_env_once


compilers = ['clang++']

def generate(env):
    """Add Builders and construction variables for clang++ to an Environment."""
    static_obj, shared_obj = SCons.Tool.createObjBuilders(env)

    SCons.Tool.cxx.generate(env)

    env['CXX']        = env.Detect(compilers) or 'clang++'

    # platform specific settings
    if env['PLATFORM'] == 'aix':
        env['SHCXXFLAGS'] = SCons.Util.CLVar('$CXXFLAGS -mminimal-toc')
        env['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 1
        env['SHOBJSUFFIX'] = '$OBJSUFFIX'
    elif env['PLATFORM'] == 'hpux':
        env['SHOBJSUFFIX'] = '.pic.o'
    elif env['PLATFORM'] == 'sunos':
        env['SHOBJSUFFIX'] = '.pic.o'
    elif env['PLATFORM'] == 'win32':
        # Ensure that we have a proper path for clang++
        clangxx = SCons.Tool.find_program_path(env, compilers[0], default_paths=get_clang_install_dirs(env['PLATFORM']))
        if clangxx:
            clangxx_bin_dir = os.path.dirname(clangxx)
            env.AppendENVPath('PATH', clangxx_bin_dir)

            # Set-up ms tools paths
            msvc_setup_env_once(env)


    # determine compiler version
    if env['CXX']:
        pipe = SCons.Action._subproc(env, [env['CXX'], '--version'],
                                     stdin='devnull',
                                     stderr='devnull',
                                     stdout=subprocess.PIPE)
        if pipe.wait() != 0: 
            return
        
        # clang -dumpversion is of no use
        with pipe.stdout:
            line = pipe.stdout.readline()
        line = line.decode()
        match = re.search(r'clang +version +([0-9]+(?:\.[0-9]+)+)', line)
        if match:
            env['CXXVERSION'] = match.group(1)

    env['CCDEPFLAGS'] = '-MMD -MF ${TARGET}.d'
    env["NINJA_DEPFILE_PARSE_FORMAT"] = 'clang'


def exists(env):
    return env.Detect(compilers)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/compilation_db.py

"""
Implements the ability for SCons to emit a compilation database for the MongoDB project. See
http://clang.llvm.org/docs/JSONCompilationDatabase.html for details on what a compilation
database is, and why you might want one. The only user visible entry point here is
'env.CompilationDatabase'. This method takes an optional 'target' to name the file that
should hold the compilation database, otherwise, the file defaults to compile_commands.json,
which is the name that most clang tools search for by default.
"""

# Copyright 2020 MongoDB Inc.
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

import json
import itertools
import fnmatch
import SCons

from .cxx import CXXSuffixes
from .cc import CSuffixes
from .asm import ASSuffixes, ASPPSuffixes

# TODO: Is there a better way to do this than this global? Right now this exists so that the
# emitter we add can record all of the things it emits, so that the scanner for the top level
# compilation database can access the complete list, and also so that the writer has easy
# access to write all of the files. But it seems clunky. How can the emitter and the scanner
# communicate more gracefully?
__COMPILATION_DB_ENTRIES = []


# We make no effort to avoid rebuilding the entries. Someday, perhaps we could and even
# integrate with the cache, but there doesn't seem to be much call for it.
class __CompilationDbNode(SCons.Node.Python.Value):
    def __init__(self, value):
        SCons.Node.Python.Value.__init__(self, value)
        self.Decider(changed_since_last_build_node)

def changed_since_last_build_node(child, target, prev_ni, node):
    """ Dummy decider to force always building"""
    return True


def make_emit_compilation_DB_entry(comstr):
    """
    Effectively this creates a lambda function to capture:
    * command line
    * source
    * target
    :param comstr: unevaluated command line
    :return: an emitter which has captured the above
    """
    user_action = SCons.Action.Action(comstr)

    def emit_compilation_db_entry(target, source, env):
        """
        This emitter will be added to each c/c++ object build to capture the info needed
        for clang tools
        :param target: target node(s)
        :param source: source node(s)
        :param env: Environment for use building this node
        :return: target(s), source(s)
        """

        dbtarget = __CompilationDbNode(source)

        entry = env.__COMPILATIONDB_Entry(
            target=dbtarget,
            source=[],
            __COMPILATIONDB_UOUTPUT=target,
            __COMPILATIONDB_USOURCE=source,
            __COMPILATIONDB_UACTION=user_action,
            __COMPILATIONDB_ENV=env,
        )

        # TODO: Technically, these next two lines should not be required: it should be fine to
        # cache the entries. However, they don't seem to update properly. Since they are quick
        # to re-generate disable caching and sidestep this problem.
        env.AlwaysBuild(entry)
        env.NoCache(entry)

        __COMPILATION_DB_ENTRIES.append(dbtarget)

        return target, source

    return emit_compilation_db_entry


def compilation_db_entry_action(target, source, env, **kw):
    """
    Create a dictionary with evaluated command line, target, source
    and store that info as an attribute on the target
    (Which has been stored in __COMPILATION_DB_ENTRIES array
    :param target: target node(s)
    :param source: source node(s)
    :param env: Environment for use building this node
    :param kw:
    :return: None
    """

    command = env["__COMPILATIONDB_UACTION"].strfunction(
        target=env["__COMPILATIONDB_UOUTPUT"],
        source=env["__COMPILATIONDB_USOURCE"],
        env=env["__COMPILATIONDB_ENV"],
    )

    entry = {
        "directory": env.Dir("#").abspath,
        "command": command,
        "file": env["__COMPILATIONDB_USOURCE"][0],
        "output": env['__COMPILATIONDB_UOUTPUT'][0]
    }

    target[0].write(entry)


def write_compilation_db(target, source, env):
    entries = []

    use_abspath = env['COMPILATIONDB_USE_ABSPATH'] in [True, 1, 'True', 'true']
    use_path_filter = env.subst('$COMPILATIONDB_PATH_FILTER')

    for s in __COMPILATION_DB_ENTRIES:
        entry = s.read()
        source_file = entry['file']
        output_file = entry['output']

        if use_abspath:
            source_file = source_file.srcnode().abspath
            output_file = output_file.abspath
        else:
            source_file = source_file.srcnode().path
            output_file = output_file.path

        if use_path_filter and not fnmatch.fnmatch(output_file, use_path_filter):
            continue

        path_entry = {'directory': entry['directory'],
                      'command': entry['command'],
                      'file': source_file,
                      'output': output_file}

        entries.append(path_entry)

    with open(target[0].path, "w") as output_file:
        json.dump(
            entries, output_file, sort_keys=True, indent=4, separators=(",", ": ")
        )


def scan_compilation_db(node, env, path):
    return __COMPILATION_DB_ENTRIES


def compilation_db_emitter(target, source, env):
    """ fix up the source/targets """

    # Someone called env.CompilationDatabase('my_targetname.json')
    if not target and len(source) == 1:
        target = source

    # Default target name is compilation_db.json
    if not target:
        target = ['compile_commands.json', ]

    # No source should have been passed. Drop it.
    if source:
        source = []

    return target, source


def generate(env, **kwargs):
    static_obj, shared_obj = SCons.Tool.createObjBuilders(env)

    env["COMPILATIONDB_COMSTR"] = kwargs.get(
        "COMPILATIONDB_COMSTR", "Building compilation database $TARGET"
    )

    components_by_suffix = itertools.chain(
        itertools.product(
            CSuffixes,
            [
                (static_obj, SCons.Defaults.StaticObjectEmitter, "$CCCOM"),
                (shared_obj, SCons.Defaults.SharedObjectEmitter, "$SHCCCOM"),
            ],
        ),
        itertools.product(
            CXXSuffixes,
            [
                (static_obj, SCons.Defaults.StaticObjectEmitter, "$CXXCOM"),
                (shared_obj, SCons.Defaults.SharedObjectEmitter, "$SHCXXCOM"),
            ],
        ),
        itertools.product(
            ASSuffixes,
            [(static_obj, SCons.Defaults.StaticObjectEmitter, "$ASCOM")],
            [(shared_obj, SCons.Defaults.SharedObjectEmitter, "$ASCOM")],
        ),
        itertools.product(
            ASPPSuffixes,
            [(static_obj, SCons.Defaults.StaticObjectEmitter, "$ASPPCOM")],
            [(shared_obj, SCons.Defaults.SharedObjectEmitter, "$ASPPCOM")],
        ),
    )

    for entry in components_by_suffix:
        suffix = entry[0]
        builder, base_emitter, command = entry[1]

        # Assumes a dictionary emitter
        emitter = builder.emitter.get(suffix, False)
        if emitter:
            # We may not have tools installed which initialize all or any of
            # cxx, cc, or assembly. If not skip resetting the respective emitter.
            builder.emitter[suffix] = SCons.Builder.ListEmitter(
                [emitter, make_emit_compilation_DB_entry(command), ]
            )

    env["BUILDERS"]["__COMPILATIONDB_Entry"] = SCons.Builder.Builder(
        action=SCons.Action.Action(compilation_db_entry_action, None),
    )

    env["BUILDERS"]["CompilationDatabase"] = SCons.Builder.Builder(
        action=SCons.Action.Action(write_compilation_db, "$COMPILATIONDB_COMSTR"),
        target_scanner=SCons.Scanner.Scanner(
            function=scan_compilation_db, node_class=None
        ),
        emitter=compilation_db_emitter,
        suffix='json',
    )

    env['COMPILATIONDB_USE_ABSPATH'] = False
    env['COMPILATIONDB_PATH_FILTER'] = ''


def exists(env):
    return True


# SCons/Tool/cvf.py

"""SCons.Tool.cvf

Tool-specific initialization for the Compaq Visual Fortran compiler.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

from . import fortran

compilers = ['f90']

def generate(env):
    """Add Builders and construction variables for compaq visual fortran to an Environment."""

    fortran.generate(env)

    env['FORTRAN']        = 'f90'
    env['FORTRANCOM']     = '$FORTRAN $FORTRANFLAGS $_FORTRANMODFLAG $_FORTRANINCFLAGS /compile_only ${SOURCES.windows} /object:${TARGET.windows}'
    env['FORTRANPPCOM']   = '$FORTRAN $FORTRANFLAGS $CPPFLAGS $_CPPDEFFLAGS $_FORTRANMODFLAG $_FORTRANINCFLAGS /compile_only ${SOURCES.windows} /object:${TARGET.windows}'
    env['SHFORTRANCOM']   = '$SHFORTRAN $SHFORTRANFLAGS $_FORTRANMODFLAG $_FORTRANINCFLAGS /compile_only ${SOURCES.windows} /object:${TARGET.windows}'
    env['SHFORTRANPPCOM'] = '$SHFORTRAN $SHFORTRANFLAGS $CPPFLAGS $_CPPDEFFLAGS $_FORTRANMODFLAG $_FORTRANINCFLAGS /compile_only ${SOURCES.windows} /object:${TARGET.windows}'
    env['OBJSUFFIX']      = '.obj'
    env['FORTRANMODDIR'] = '${TARGET.dir}'
    env['FORTRANMODDIRPREFIX'] = '/module:'
    env['FORTRANMODDIRSUFFIX'] = ''

def exists(env):
    return env.Detect(compilers)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/cxx.py

"""SCons.Tool.c++

Tool-specific initialization for generic Posix C++ compilers.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""

#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

import os.path

import SCons.Defaults
import SCons.Util

compilers = ['CC', 'c++']

CXXSuffixes = ['.cpp', '.cc', '.cxx', '.c++', '.C++', '.mm']
if SCons.Util.case_sensitive_suffixes('.c', '.C'):
    CXXSuffixes.append('.C')

def iscplusplus(source):
    if not source:
        # Source might be None for unusual cases like SConf.
        return 0
    for s in source:
        if s.sources:
            ext = os.path.splitext(str(s.sources[0]))[1]
            if ext in CXXSuffixes:
                return 1
    return 0

def generate(env):
    """
    Add Builders and construction variables for Visual Age C++ compilers
    to an Environment.
    """
    import SCons.Tool
    import SCons.Tool.cc
    static_obj, shared_obj = SCons.Tool.createObjBuilders(env)

    for suffix in CXXSuffixes:
        static_obj.add_action(suffix, SCons.Defaults.CXXAction)
        shared_obj.add_action(suffix, SCons.Defaults.ShCXXAction)
        static_obj.add_emitter(suffix, SCons.Defaults.StaticObjectEmitter)
        shared_obj.add_emitter(suffix, SCons.Defaults.SharedObjectEmitter)

    SCons.Tool.cc.add_common_cc_variables(env)

    if 'CXX' not in env:
        env['CXX']    = env.Detect(compilers) or compilers[0]
    env['CXXFLAGS']   = SCons.Util.CLVar('')
    env['CXXCOM']     = '$CXX -o $TARGET -c $CXXFLAGS $CCFLAGS $_CCCOMCOM $SOURCES'
    env['SHCXX']      = '$CXX'
    env['SHCXXFLAGS'] = SCons.Util.CLVar('$CXXFLAGS')
    env['SHCXXCOM']   = '$SHCXX -o $TARGET -c $SHCXXFLAGS $SHCCFLAGS $_CCCOMCOM $SOURCES'

    env['CPPDEFPREFIX']  = '-D'
    env['CPPDEFSUFFIX']  = ''
    env['INCPREFIX']  = '-I'
    env['INCSUFFIX']  = ''
    env['SHOBJSUFFIX'] = '.os'
    env['OBJSUFFIX'] = '.o'
    env['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 0

    env['CXXFILESUFFIX'] = '.cc'

def exists(env):
    return env.Detect(env.get('CXX', compilers))

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/cyglink.py

"""SCons.Tool.cyglink

Customization of gnulink for Cygwin (http://www.cygwin.com/)

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

from SCons.Tool.linkCommon import StringizeLibSymlinks, EmitLibSymlinks
from SCons.Util import CLVar, is_String
from . import gnulink


def cyglink_lib_emitter(target, source, env, **kw):
    verbose = True

    if 'variable_prefix' in kw:
        var_prefix = kw['variable_prefix']
    else:
        var_prefix = 'SHLIB'

    no_import_lib = env.get('no_import_lib', False)

    if verbose:
        print("cyglink_lib_emitter: target[0]={!r}".format(target[0].get_path()))

    if not no_import_lib:
        # Specify import lib and add to targets

        import_lib = env.subst('$%s_IMPLIBNAME' % var_prefix, target=target, source=source)
        import_lib_target = env.fs.File(import_lib)
        import_lib_target.attributes.shared = 1
        target.append(import_lib_target)

        if verbose:
            print("cyglink_lib_emitter: import_lib={}".format(import_lib))
            print("cyglink_lib_emitter: target=%s" % target)

    for tgt in target:
        if is_String(tgt):
            tgt = env.File(tgt)
        tgt.attributes.shared = 1

    return target, source


def cyglink_ldmodule_emitter(target, source, env, **kw):
    return cyglink_lib_emitter(target, source, env, variable_prefix='LDMODULE')


def cyglink_shlib_symlink_emitter(target, source, env, **kw):
    """
    On cygwin, we only create a symlink from the non-versioned implib to the versioned implib.
    We don't version the shared library itself.
    :param target:
    :param source:
    :param env:
    :param kw:
    :return:
    """
    verbose = True

    if 'variable_prefix' in kw:
        var_prefix = kw['variable_prefix']
    else:
        var_prefix = 'SHLIB'

    no_import_lib = env.get('no_import_lib', False)
    if no_import_lib in ['1', 'True', 'true', True]:
        if verbose:
            print("cyglink_shlib_symlink_emitter: no_import_lib=%s" % no_import_lib)
        return target, source

    no_symlinks = env.subst('$%sNOVERSIONSYMLINKS' % var_prefix)
    if no_symlinks in ['1', 'True', 'true', True]:
        return target, source

    shlibversion = env.subst('$%sVERSION' % var_prefix)
    if shlibversion:
        if verbose:
            print("cyglink_shlib_symlink_emitter: %sVERSION=%s" % (var_prefix, shlibversion))

        # The implib (added by the cyglink_lib_emitter)
        imp_lib_node = target[1]
        shlib_noversion_symlink = env.subst('$%s_NOVERSION_SYMLINK' % var_prefix, target=target[0], source=source)

        if verbose:
            print("cyglink_shlib_symlink_emitter: shlib_noversion_symlink :%s" % shlib_noversion_symlink)
            print("cyglink_shlib_symlink_emitter: imp_lib_node            :%s" % imp_lib_node)

        symlinks = [(env.File(shlib_noversion_symlink), imp_lib_node)]

        if verbose:
            print("cyglink_shlib_symlink_emitter: symlinks={!r}".format(
                ', '.join(["%r->%r" % (k, v) for k, v in StringizeLibSymlinks(symlinks)])
            ))

        if symlinks:
            # This does the actual symlinking
            EmitLibSymlinks(env, symlinks, target[0])

            # This saves the information so if the versioned shared library is installed
            # it can faithfully reproduce the correct symlinks
            target[0].attributes.shliblinks = symlinks

    return target, source


def cyglink_ldmod_symlink_emitter(target, source, env, **kw):
    return cyglink_shlib_symlink_emitter(target, source, env, variable_prefix='LDMODULE')


def cyglink_shlibversion(target, source, env, for_signature):
    var_prefix = 'SHLIB'
    var = '%sVERSION' % var_prefix
    if var not in env:
        return ''

    version = env.subst("$%s" % var, target=target, source=source)
    version = version.replace('.', '-')
    return "." + version


def cyglink_ldmodule_version(target, source, env, for_signature):
    var_prefix = 'LDMODULE'
    var = '%sVERSION' % var_prefix
    if var not in env:
        return ''

    version = env.subst("$%s" % var, target=target, source=source)
    version = version.replace('.', '-')
    return "." + version


def _implib_pre_flags(target, source, env, for_signature):
    no_import_lib = env.get('no_import_lib', False)
    if no_import_lib in ['1', 'True', 'true', True]:
        return ''
    else:
        return '-Wl,--out-implib=${TARGETS[1]} -Wl,--export-all-symbols -Wl,--enable-auto-import -Wl,--whole-archive'


def _implib_post_flags(target, source, env, for_signature):
    no_import_lib = env.get('no_import_lib', False)
    if no_import_lib in ['1', 'True', 'true', True]:
        return ''
    else:
        return '-Wl,--no-whole-archive'


def generate(env):
    """Add Builders and construction variables for cyglink to an Environment."""
    gnulink.generate(env)

    env['LINKFLAGS'] = CLVar('-Wl,-no-undefined')

    env['SHLIBPREFIX'] = 'cyg'
    env['SHLIBSUFFIX'] = '.dll'

    env['IMPLIBPREFIX'] = 'lib'
    env['IMPLIBSUFFIX'] = '.dll.a'

    # Variables used by versioned shared libraries
    # SHLIBVERSIONFLAGS and LDMODULEVERSIONFLAGS are same as in gnulink...
    env['_SHLIBVERSIONFLAGS'] = '$SHLIBVERSIONFLAGS'
    env['_LDMODULEVERSIONFLAGS'] = '$LDMODULEVERSIONFLAGS'

    env['_IMPLIB_PRE_SOURCES'] = _implib_pre_flags
    env['_IMPLIB_POST_SOURCES'] = _implib_post_flags
    env['SHLINKCOM'] = '$SHLINK -o $TARGET $SHLINKFLAGS $__SHLIBVERSIONFLAGS $__RPATH ' \
                       '$_IMPLIB_PRE_SOURCES $SOURCES  $_IMPLIB_POST_SOURCES $_LIBDIRFLAGS $_LIBFLAGS'
    env['LDMODULECOM'] = '$LDMODULE -o $TARGET $SHLINKFLAGS $__LDMODULEVERSIONFLAGS $__RPATH ' \
                         '$_IMPLIB_PRE_SOURCES $SOURCES $_IMPLIB_POST_SOURCES $_LIBDIRFLAGS  $_LIBFLAGS'

    # Overwrite emitters. Cyglink does things differently when creating symlinks
    env['SHLIBEMITTER'] = [cyglink_lib_emitter, cyglink_shlib_symlink_emitter]
    env['LDMODULEEMITTER'] = [cyglink_ldmodule_emitter, cyglink_ldmod_symlink_emitter]

    # This is the non versioned shlib filename
    # If SHLIBVERSION is defined then this will symlink to $SHLIBNAME
    env['SHLIB_NOVERSION_SYMLINK'] = '${IMPLIBPREFIX}$_get_shlib_stem${IMPLIBSUFFIX}'
    env['LDMODULE_NOVERSION_SYMLINK'] = '${IMPLIBPREFIX}$_get_ldmodule_stem${IMPLIBSUFFIX}'

    env['SHLIB_IMPLIBNAME'] = '${IMPLIBPREFIX}$_get_shlib_stem${_SHLIB_IMPLIBSUFFIX}'
    env['LDMODULE_IMPLIBNAME'] = '${IMPLIBPREFIX}$_get_ldmodule_stem${_LDMODULE_IMPLIBSUFFIX}'

    env['_cyglink_shlibversion'] = cyglink_shlibversion
    env['_SHLIB_IMPLIBSUFFIX'] = '${_cyglink_shlibversion}${IMPLIBSUFFIX}'
    env['_SHLIBSUFFIX'] = '${_cyglink_shlibversion}${SHLIBSUFFIX}'

    env['_cyglink_ldmodule_version'] = cyglink_ldmodule_version

    env['_LDMODULESUFFIX'] = '${_cyglink_ldmodule_version}${LDMODULESUFFIX}'
    env['_LDMODULE_IMPLIBSUFFIX'] = '${_cyglink_ldmodule_version}${IMPLIBSUFFIX}'

    # Remove variables set by default initialization which aren't needed/used by cyglink
    # these variables were set by gnulink but are not used in cyglink
    for rv in ['_SHLIBSONAME', '_LDMODULESONAME']:
        if rv in env:
            del env[rv]


def exists(env):
    return gnulink.exists(env)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/default.py

"""SCons.Tool.default

Initialization with a default tool list.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import SCons.Tool

def generate(env):
    """Add default tools."""
    for t in SCons.Tool.tool_list(env['PLATFORM'], env):
        SCons.Tool.Tool(t)(env)

def exists(env):
    return 1

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/dmd.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""SCons.Tool.dmd

Tool-specific initialization for the Digital Mars D compiler.
(http://digitalmars.com/d)

Originally coded by Andy Friesen (andy@ikagames.com)
15 November 2003

Evolved by Russel Winder (russel@winder.org.uk)
2010-02-07 onwards

Compiler variables:

DC
  The name of the D compiler to use.
  Defaults to dmd or gdmd, whichever is found.

DPATH
  List of paths to search for import modules.

DVERSIONS
  List of version tags to enable when compiling.

DDEBUG
  List of debug tags to enable when compiling.

Linker related variables:

LIBS
  List of library files to link in.

DLINK
  Name of the linker to use.
  Defaults to dmd or gdmd, whichever is found.

DLINKFLAGS
  List of linker flags.

Lib tool variables:

DLIB
  Name of the lib tool to use.  Defaults to lib.

DLIBFLAGS
  List of flags to pass to the lib tool.

LIBS
  Same as for the linker. (libraries to pull into the .lib)

"""


import SCons.Action
import SCons.Builder
import SCons.Defaults
import SCons.Scanner.D
import SCons.Tool

import SCons.Tool.DCommon as DCommon


def generate(env):
    static_obj, shared_obj = SCons.Tool.createObjBuilders(env)

    static_obj.add_action('.d', SCons.Defaults.DAction)
    shared_obj.add_action('.d', SCons.Defaults.ShDAction)
    static_obj.add_emitter('.d', SCons.Defaults.StaticObjectEmitter)
    shared_obj.add_emitter('.d', SCons.Defaults.SharedObjectEmitter)

    env['DC'] = env.Detect(['dmd', 'ldmd2', 'gdmd']) or 'dmd'
    env['DCOM'] = '$DC $_DINCFLAGS $_DVERFLAGS $_DDEBUGFLAGS $_DFLAGS -c -of$TARGET $SOURCES'
    env['_DINCFLAGS'] = '${_concat(DINCPREFIX, DPATH, DINCSUFFIX, __env__, RDirs, TARGET, SOURCE)}'
    env['_DVERFLAGS'] = '${_concat(DVERPREFIX, DVERSIONS, DVERSUFFIX, __env__)}'
    env['_DDEBUGFLAGS'] = '${_concat(DDEBUGPREFIX, DDEBUG, DDEBUGSUFFIX, __env__)}'
    env['_DFLAGS'] = '${_concat(DFLAGPREFIX, DFLAGS, DFLAGSUFFIX, __env__)}'

    env['SHDC'] = '$DC'
    env['SHDCOM'] = '$DC $_DINCFLAGS $_DVERFLAGS $_DDEBUGFLAGS $_DFLAGS -c -fPIC -of$TARGET $SOURCES'

    env['DPATH'] = ['#/']
    env['DFLAGS'] = []
    env['DVERSIONS'] = []
    env['DDEBUG'] = []

    if env['DC']:
        DCommon.addDPATHToEnv(env, env['DC'])

    env['DINCPREFIX'] = '-I'
    env['DINCSUFFIX'] = ''
    env['DVERPREFIX'] = '-version='
    env['DVERSUFFIX'] = ''
    env['DDEBUGPREFIX'] = '-debug='
    env['DDEBUGSUFFIX'] = ''
    env['DFLAGPREFIX'] = '-'
    env['DFLAGSUFFIX'] = ''
    env['DFILESUFFIX'] = '.d'

    env['DLINK'] = '$DC'
    env['DLINKFLAGS'] = SCons.Util.CLVar('')
    env['DLINKCOM'] = '$DLINK -of$TARGET $DLINKFLAGS $__DRPATH $SOURCES $_DLIBDIRFLAGS $_DLIBFLAGS'

    env['SHDLINK'] = '$DC'
    env['SHDLINKFLAGS'] = SCons.Util.CLVar('$DLINKFLAGS -shared -defaultlib=libphobos2.so')
    env[
        'SHDLINKCOM'] = '$DLINK -of$TARGET $SHDLINKFLAGS $__SHDLIBVERSIONFLAGS $__DRPATH $SOURCES $_DLIBDIRFLAGS $_DLIBFLAGS'

    env['DLIBLINKPREFIX'] = '' if env['PLATFORM'] == 'win32' else '-L-l'
    env['DLIBLINKSUFFIX'] = '.lib' if env['PLATFORM'] == 'win32' else ''
    env['_DLIBFLAGS'] = '${_stripixes(DLIBLINKPREFIX, LIBS, DLIBLINKSUFFIX, LIBPREFIXES, LIBSUFFIXES,  __env__)}'

    env['DLIBDIRPREFIX'] = '-L-L'
    env['DLIBDIRSUFFIX'] = ''
    env['_DLIBDIRFLAGS'] = '${_concat(DLIBDIRPREFIX, LIBPATH, DLIBDIRSUFFIX, __env__, RDirs, TARGET, SOURCE)}'

    env['DLIB'] = 'lib' if env['PLATFORM'] == 'win32' else 'ar cr'
    env['DLIBCOM'] = '$DLIB $_DLIBFLAGS {0}$TARGET $SOURCES $_DLIBFLAGS'.format(
        '-c ' if env['PLATFORM'] == 'win32' else '')

    # env['_DLIBFLAGS'] = '${_concat(DLIBFLAGPREFIX, DLIBFLAGS, DLIBFLAGSUFFIX, __env__)}'

    env['DLIBFLAGPREFIX'] = '-'
    env['DLIBFLAGSUFFIX'] = ''

    # __RPATH is set to $_RPATH in the platform specification if that
    # platform supports it.
    env['DRPATHPREFIX'] = '-L-rpath,' if env['PLATFORM'] == 'darwin' else '-L-rpath='
    env['DRPATHSUFFIX'] = ''
    env['_DRPATH'] = '${_concat(DRPATHPREFIX, RPATH, DRPATHSUFFIX, __env__)}'

    # Support for versioned libraries
    env['_SHDLIBVERSIONFLAGS'] = '$SHDLIBVERSIONFLAGS -L-soname=$_SHLIBSONAME'

    env['BUILDERS']['ProgramAllAtOnce'] = SCons.Builder.Builder(
        action='$DC $_DINCFLAGS $_DVERFLAGS $_DDEBUGFLAGS $_DFLAGS -of$TARGET $DLINKFLAGS $__DRPATH $SOURCES $_DLIBDIRFLAGS $_DLIBFLAGS',
        emitter=DCommon.allAtOnceEmitter,
    )


def exists(env):
    return env.Detect(['dmd', 'ldmd2', 'gdmd'])

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/dvi.py

"""SCons.Tool.dvi

Common DVI Builder definition for various other Tool modules that use it.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import SCons.Builder
import SCons.Tool

DVIBuilder = None

def generate(env):
    try:
        env['BUILDERS']['DVI']
    except KeyError:
        global DVIBuilder

        if DVIBuilder is None:
            # The suffix is hard-coded to '.dvi', not configurable via a
            # construction variable like $DVISUFFIX, because the output
            # file name is hard-coded within TeX.
            DVIBuilder = SCons.Builder.Builder(action = {},
                                               source_scanner = SCons.Tool.LaTeXScanner,
                                               suffix = '.dvi',
                                               emitter = {},
                                               source_ext_match = None)

        env['BUILDERS']['DVI'] = DVIBuilder

def exists(env):
    # This only puts a skeleton Builder in place, so if someone
    # references this Tool directly, it's always "available."
    return 1

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/dvipdf.py

"""SCons.Tool.dvipdf

Tool-specific initialization for dvipdf.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import SCons.Action
import SCons.Defaults
import SCons.Tool.pdf
import SCons.Tool.tex
import SCons.Util

_null = SCons.Scanner.LaTeX._null

def DviPdfPsFunction(XXXDviAction, target = None, source= None, env=None):
    """A builder for DVI files that sets the TEXPICTS environment
       variable before running dvi2ps or dvipdf."""

    try:
        abspath = source[0].attributes.path
    except AttributeError :
        abspath =  ''

    saved_env = SCons.Scanner.LaTeX.modify_env_var(env, 'TEXPICTS', abspath)

    result = XXXDviAction(target, source, env)

    if saved_env is _null:
        try:
            del env['ENV']['TEXPICTS']
        except KeyError:
            pass # was never set
    else:
        env['ENV']['TEXPICTS'] = saved_env

    return result

def DviPdfFunction(target = None, source= None, env=None):
    result = DviPdfPsFunction(PDFAction,target,source,env)
    return result

def DviPdfStrFunction(target = None, source= None, env=None):
    """A strfunction for dvipdf that returns the appropriate
    command string for the no_exec options."""
    if env.GetOption("no_exec"):
        result = env.subst('$DVIPDFCOM',0,target,source)
    else:
        result = ''
    return result

PDFAction = None
DVIPDFAction = None

def PDFEmitter(target, source, env):
    """Strips any .aux or .log files from the input source list.
    These are created by the TeX Builder that in all likelihood was
    used to generate the .dvi file we're using as input, and we only
    care about the .dvi file.
    """
    def strip_suffixes(n):
        return not SCons.Util.splitext(str(n))[1] in ['.aux', '.log']
    source = [src for src in source if strip_suffixes(src)]
    return (target, source)

def generate(env):
    """Add Builders and construction variables for dvipdf to an Environment."""
    global PDFAction
    if PDFAction is None:
        PDFAction = SCons.Action.Action('$DVIPDFCOM', '$DVIPDFCOMSTR')

    global DVIPDFAction
    if DVIPDFAction is None:
        DVIPDFAction = SCons.Action.Action(DviPdfFunction, strfunction = DviPdfStrFunction)

    from . import pdf
    pdf.generate(env)

    bld = env['BUILDERS']['PDF']
    bld.add_action('.dvi', DVIPDFAction)
    bld.add_emitter('.dvi', PDFEmitter)

    env['DVIPDF']      = 'dvipdf'
    env['DVIPDFFLAGS'] = SCons.Util.CLVar('')
    env['DVIPDFCOM']   = 'cd ${TARGET.dir} && $DVIPDF $DVIPDFFLAGS ${SOURCE.file} ${TARGET.file}'

def exists(env):
    SCons.Tool.tex.generate_darwin(env)
    return env.Detect('dvipdf')

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/dvips.py

"""SCons.Tool.dvips

Tool-specific initialization for dvips.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import SCons.Action
import SCons.Builder
import SCons.Tool.dvipdf
import SCons.Util

def DviPsFunction(target = None, source= None, env=None):
    result = SCons.Tool.dvipdf.DviPdfPsFunction(PSAction,target,source,env)
    return result

def DviPsStrFunction(target = None, source= None, env=None):
    """A strfunction for dvipdf that returns the appropriate
    command string for the no_exec options."""
    if env.GetOption("no_exec"):
        result = env.subst('$PSCOM',0,target,source)
    else:
        result = ''
    return result

PSAction = None
DVIPSAction = None
PSBuilder = None

def generate(env):
    """Add Builders and construction variables for dvips to an Environment."""
    global PSAction
    if PSAction is None:
        PSAction = SCons.Action.Action('$PSCOM', '$PSCOMSTR')

    global DVIPSAction
    if DVIPSAction is None:
        DVIPSAction = SCons.Action.Action(DviPsFunction, strfunction = DviPsStrFunction)

    global PSBuilder
    if PSBuilder is None:
        PSBuilder = SCons.Builder.Builder(action = PSAction,
                                          prefix = '$PSPREFIX',
                                          suffix = '$PSSUFFIX',
                                          src_suffix = '.dvi',
                                          src_builder = 'DVI',
                                          single_source=True)

    env['BUILDERS']['PostScript'] = PSBuilder
    
    env['DVIPS']      = 'dvips'
    env['DVIPSFLAGS'] = SCons.Util.CLVar('')
    # I'm not quite sure I got the directories and filenames right for variant_dir
    # We need to be in the correct directory for the sake of latex \includegraphics eps included files.
    env['PSCOM']      = 'cd ${TARGET.dir} && $DVIPS $DVIPSFLAGS -o ${TARGET.file} ${SOURCE.file}'
    env['PSPREFIX'] = ''
    env['PSSUFFIX'] = '.ps'

def exists(env):
    SCons.Tool.tex.generate_darwin(env)
    return env.Detect('dvips')

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/f03.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""Tool-specific initialization for the generic Posix f03 Fortran compiler.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""

from SCons.Tool.FortranCommon import add_all_to_env, add_f03_to_env

compilers = ['f03']

def generate(env):
    add_all_to_env(env)
    add_f03_to_env(env)

    fcomp = env.Detect(compilers) or 'f03'
    if 'F03' not in env:
        env['F03']  = fcomp
    if 'SHF03' not in env:
        env['SHF03']  = '$F03'

    if 'FORTRAN' not in env:
        env['FORTRAN']  = fcomp
    if 'SHFORTRAN' not in env:
        env['SHFORTRAN']  = '$FORTRAN'


def exists(env):
    return env.Detect(compilers)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/f08.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""Tool-specific initialization for the generic Posix f08 Fortran compiler.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""

from SCons.Tool.FortranCommon import add_all_to_env, add_f08_to_env

compilers = ['f08']

def generate(env):
    add_all_to_env(env)
    add_f08_to_env(env)

    fcomp = env.Detect(compilers) or 'f08'
    if 'F08' not in env:
        env['F08']  = fcomp
    if 'SHF08' not in env:
        env['SHF08']  = '$F08'

    if 'FORTRAN' not in env:
        env['FORTRAN']  = fcomp
    if 'SHFORTRAN' not in env:
        env['SHFORTRAN']  = '$FORTRAN'


def exists(env):
    return env.Detect(compilers)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/f77.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""Tool-specific initialization for the generic Posix f77 Fortran compiler.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""

from SCons.Tool.FortranCommon import add_all_to_env, add_f77_to_env

compilers = ['f77']

def generate(env):
    add_all_to_env(env)
    add_f77_to_env(env)

    fcomp = env.Detect(compilers) or 'f77'
    if 'F77' not in env:
        env['F77'] = fcomp
    if 'SHF77' not in env:
        env['SHF77']  = '$F77'

    if 'FORTRAN' not in env:
        env['FORTRAN']  = fcomp
    if 'SHFORTRAN' not in env:
        env['SHFORTRAN']  = '$FORTRAN'

def exists(env):
    return env.Detect(compilers)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/f90.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""Tool-specific initialization for the generic Posix f90 Fortran compiler.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""

from SCons.Tool.FortranCommon import add_all_to_env, add_f90_to_env

compilers = ['f90']

def generate(env):
    add_all_to_env(env)
    add_f90_to_env(env)

    fc = env.Detect(compilers) or 'f90'
    if 'F90' not in env:
        env['F90']  = fc
    if 'SHF90' not in env:
        env['SHF90']  = '$F90'

    if 'FORTRAN' not in env:
        env['FORTRAN']  = fc
    if 'SHFORTRAN' not in env:
        env['SHFORTRAN']  = '$FORTRAN'

def exists(env):
    return env.Detect(compilers)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/f95.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""Tool-specific initialization for the generic Posix f95 Fortran compiler.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""

from SCons.Tool.FortranCommon import add_all_to_env, add_f95_to_env

compilers = ['f95']

def generate(env):
    add_all_to_env(env)
    add_f95_to_env(env)

    fcomp = env.Detect(compilers) or 'f95'
    if 'F95' not in env:
        env['F95']  = fcomp
    if 'SHF95' not in env:
        env['SHF95']  = '$F95'

    if 'FORTRAN' not in env:
        env['FORTRAN']  = fcomp
    if 'SHFORTRAN' not in env:
        env['SHFORTRAN']  = '$FORTRAN'


def exists(env):
    return env.Detect(compilers)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/filesystem.py

"""SCons.Tool.filesystem

Tool-specific initialization for the filesystem tools.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import SCons
from SCons.Tool.install import copyFunc

copyToBuilder, copyAsBuilder = None, None

def copyto_emitter(target, source, env):
    """ changes the path of the source to be under the target (which
    are assumed to be directories.
    """
    n_target = []

    for t in target:
        n_target = n_target + [t.File( str( s ) ) for s in source]

    return (n_target, source)

def copy_action_func(target, source, env):
    assert( len(target) == len(source) ), "\ntarget: %s\nsource: %s" %(list(map(str, target)),list(map(str, source)))

    for t, s in zip(target, source):
        if copyFunc(t.get_path(), s.get_path(), env):
            return 1

    return 0

def copy_action_str(target, source, env):
    return env.subst_target_source(env['COPYSTR'], 0, target, source)

copy_action = SCons.Action.Action( copy_action_func, copy_action_str )

def generate(env):
    try:
        env['BUILDERS']['CopyTo']
        env['BUILDERS']['CopyAs']
    except KeyError as e:
        global copyToBuilder
        if copyToBuilder is None:
            copyToBuilder = SCons.Builder.Builder(
                             action         = copy_action,
                             target_factory = env.fs.Dir,
                             source_factory = env.fs.Entry,
                             multi          = 1,
                             emitter        = [ copyto_emitter, ] )

        global copyAsBuilder
        if copyAsBuilder is None:
            copyAsBuilder = SCons.Builder.Builder(
                             action         = copy_action,
                             target_factory = env.fs.Entry,
                             source_factory = env.fs.Entry )

        env['BUILDERS']['CopyTo'] = copyToBuilder
        env['BUILDERS']['CopyAs'] = copyAsBuilder

        env['COPYSTR'] = 'Copy file(s): "$SOURCES" to "$TARGETS"'

def exists(env):
    return 1

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/fortran.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""Tool-specific initialization for a generic Posix f77/f90 Fortran compiler.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""

from SCons.Tool.FortranCommon import add_all_to_env, add_fortran_to_env

compilers = ['f95', 'f90', 'f77']


def generate(env):
    add_all_to_env(env)
    add_fortran_to_env(env)

    if 'FORTRAN' not in env:
        env['FORTRAN'] = env.Detect(compilers) or 'f77'
    if 'SHFORTRAN' not in env:
        env['SHFORTRAN'] = '$FORTRAN'


def exists(env):
    return env.Detect(compilers)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/g++.py

"""SCons.Tool.g++

Tool-specific initialization for g++.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"


#forward proxy to the preffered cxx version
from SCons.Tool.gxx import *


# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/g77.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""

Tool-specific initialization for g77.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

import SCons.Util
from SCons.Tool.FortranCommon import add_all_to_env, add_f77_to_env

compilers = ['g77', 'f77']


def generate(env):
    """Add Builders and construction variables for g77 to an Environment."""
    add_all_to_env(env)
    add_f77_to_env(env)

    fcomp = env.Detect(compilers) or 'g77'
    if env['PLATFORM'] in ['cygwin', 'win32']:
        env['SHFORTRANFLAGS'] = SCons.Util.CLVar('$FORTRANFLAGS')
        env['SHF77FLAGS'] = SCons.Util.CLVar('$F77FLAGS')
    else:
        env['SHFORTRANFLAGS'] = SCons.Util.CLVar('$FORTRANFLAGS -fPIC')
        env['SHF77FLAGS'] = SCons.Util.CLVar('$F77FLAGS -fPIC')

    env['FORTRAN'] = fcomp
    env['SHFORTRAN'] = '$FORTRAN'

    env['F77'] = fcomp
    env['SHF77'] = '$F77'

    env['INCFORTRANPREFIX'] = "-I"
    env['INCFORTRANSUFFIX'] = ""

    env['INCF77PREFIX'] = "-I"
    env['INCF77SUFFIX'] = ""


def exists(env):
    return env.Detect(compilers)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/gas.py

"""SCons.Tool.gas

Tool-specific initialization for as, the Gnu assembler.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

try:
    as_module = __import__('as', globals(), locals(), [])
except:
    as_module = __import__(__package__+'.as', globals(), locals(), ['*'])

assemblers = ['as', 'gas']

def generate(env):
    """Add Builders and construction variables for as to an Environment."""
    as_module.generate(env)

    env['AS']        = env.Detect(assemblers) or 'as'

def exists(env):
    return env.Detect(assemblers)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/gcc.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""SCons.Tool.gcc

Tool-specific initialization for gcc.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

from . import cc
import re
import subprocess

import SCons.Util

compilers = ['gcc', 'cc']


def generate(env):
    """Add Builders and construction variables for gcc to an Environment."""

    if 'CC' not in env:
        env['CC'] = env.Detect(compilers) or compilers[0]

    cc.generate(env)

    if env['PLATFORM'] in ['cygwin', 'win32']:
        env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS')
    else:
        env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS -fPIC')
    # determine compiler version
    version = detect_version(env, env['CC'])
    if version:
        env['CCVERSION'] = version

    env['CCDEPFLAGS'] = '-MMD -MF ${TARGET}.d'
    env["NINJA_DEPFILE_PARSE_FORMAT"] = 'gcc'



def exists(env):
    # is executable, and is a GNU compiler (or accepts '--version' at least)
    return detect_version(env, env.Detect(env.get('CC', compilers)))


def detect_version(env, cc):
    """Return the version of the GNU compiler, or None if it is not a GNU compiler."""
    version = None
    cc = env.subst(cc)
    if not cc:
        return version

    # -dumpversion was added in GCC 3.0.  As long as we're supporting
    # GCC versions older than that, we should use --version and a
    # regular expression.
    # pipe = SCons.Action._subproc(env, SCons.Util.CLVar(cc) + ['-dumpversion'],
    with SCons.Action._subproc(env, SCons.Util.CLVar(cc) + ['--version'],
                                 stdin='devnull',
                                 stderr='devnull',
                                 stdout=subprocess.PIPE) as pipe:
        if pipe.wait() != 0:
            return version

        # -dumpversion variant:
        # line = pipe.stdout.read().strip()
        # --version variant:
        line = SCons.Util.to_str(pipe.stdout.readline())
        # Non-GNU compiler's output (like AIX xlc's) may exceed the stdout buffer:
        # So continue with reading to let the child process actually terminate.
        # We don't need to know the rest of the data, so don't bother decoding.
        while pipe.stdout.readline():
            pass


    # -dumpversion variant:
    # if line:
    #     version = line
    # --version variant:
    match = re.search(r'[0-9]+(\.[0-9]+)+', line)
    if match:
        version = match.group(0)

    return version

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/gdc.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""SCons.Tool.gdc

Tool-specific initialization for the GDC compiler.
(https://github.com/D-Programming-GDC/GDC)

Developed by Russel Winder (russel@winder.org.uk)
2012-05-09 onwards

Compiler variables:
    DC - The name of the D compiler to use.  Defaults to gdc.
    DPATH - List of paths to search for import modules.
    DVERSIONS - List of version tags to enable when compiling.
    DDEBUG - List of debug tags to enable when compiling.

Linker related variables:
    LIBS - List of library files to link in.
    DLINK - Name of the linker to use.  Defaults to gdc.
    DLINKFLAGS - List of linker flags.

Lib tool variables:
    DLIB - Name of the lib tool to use.  Defaults to lib.
    DLIBFLAGS - List of flags to pass to the lib tool.
    LIBS - Same as for the linker. (libraries to pull into the .lib)
"""

import SCons.Action
import SCons.Defaults
import SCons.Tool

import SCons.Tool.DCommon as DCommon
import SCons.Tool.linkCommon


def generate(env):
    static_obj, shared_obj = SCons.Tool.createObjBuilders(env)

    static_obj.add_action('.d', SCons.Defaults.DAction)
    shared_obj.add_action('.d', SCons.Defaults.ShDAction)
    static_obj.add_emitter('.d', SCons.Defaults.StaticObjectEmitter)
    shared_obj.add_emitter('.d', SCons.Defaults.SharedObjectEmitter)

    env['DC'] = env.Detect('gdc') or 'gdc'
    env['DCOM'] = '$DC $_DINCFLAGS $_DVERFLAGS $_DDEBUGFLAGS $_DFLAGS -c -o $TARGET $SOURCES'
    env['_DINCFLAGS'] = '${_concat(DINCPREFIX, DPATH, DINCSUFFIX, __env__, RDirs, TARGET, SOURCE)}'
    env['_DVERFLAGS'] = '${_concat(DVERPREFIX, DVERSIONS, DVERSUFFIX, __env__)}'
    env['_DDEBUGFLAGS'] = '${_concat(DDEBUGPREFIX, DDEBUG, DDEBUGSUFFIX, __env__)}'
    env['_DFLAGS'] = '${_concat(DFLAGPREFIX, DFLAGS, DFLAGSUFFIX, __env__)}'

    env['SHDC'] = '$DC'
    env['SHDCOM'] = '$SHDC $_DINCFLAGS $_DVERFLAGS $_DDEBUGFLAGS $_DFLAGS -fPIC -c -o $TARGET $SOURCES'

    env['DPATH'] = ['#/']
    env['DFLAGS'] = []
    env['DVERSIONS'] = []
    env['DDEBUG'] = []

    if env['DC']:
        DCommon.addDPATHToEnv(env, env['DC'])

    env['DINCPREFIX'] = '-I'
    env['DINCSUFFIX'] = ''
    env['DVERPREFIX'] = '-version='
    env['DVERSUFFIX'] = ''
    env['DDEBUGPREFIX'] = '-debug='
    env['DDEBUGSUFFIX'] = ''
    env['DFLAGPREFIX'] = '-'
    env['DFLAGSUFFIX'] = ''
    env['DFILESUFFIX'] = '.d'

    env['DLINK'] = '$DC'
    env['DLINKFLAGS'] = SCons.Util.CLVar('')
    env['DLINKCOM'] = '$DLINK -o $TARGET $DLINKFLAGS $__RPATH $SOURCES $_LIBDIRFLAGS $_LIBFLAGS'

    env['SHDLINK'] = '$DC'
    env['SHDLINKFLAGS'] = SCons.Util.CLVar('$DLINKFLAGS -shared -shared-libphobos')
    env['SHDLINKCOM'] = '$DLINK -o $TARGET $SHDLINKFLAGS $__SHDLIBVERSIONFLAGS $__RPATH $SOURCES $_LIBDIRFLAGS $_LIBFLAGS'

    env['DLIB'] = 'lib' if env['PLATFORM'] == 'win32' else 'ar cr'
    env['DLIBCOM'] = '$DLIB $_DLIBFLAGS {0}$TARGET $SOURCES $_DLINKLIBFLAGS'.format('-c ' if env['PLATFORM'] == 'win32' else '')

    env['_DLIBFLAGS'] = '${_concat(DLIBFLAGPREFIX, DLIBFLAGS, DLIBFLAGSUFFIX, __env__)}'

    env['DLIBFLAGPREFIX'] = '-'
    env['DLIBFLAGSUFFIX'] = ''
    env['DLINKFLAGPREFIX'] = '-'
    env['DLINKFLAGSUFFIX'] = ''

    # __RPATH is set to $_RPATH in the platform specification if that
    # platform supports it.
    env['RPATHPREFIX'] = '-Wl,-rpath='
    env['RPATHSUFFIX'] = ''
    env['_RPATH'] = '${_concat(RPATHPREFIX, RPATH, RPATHSUFFIX, __env__)}'

    # Support for versioned libraries
    env['_SHDLIBVERSIONFLAGS'] = '$SHDLIBVERSIONFLAGS -Wl,-soname=$_SHLIBSONAME'

    env['BUILDERS']['ProgramAllAtOnce'] = SCons.Builder.Builder(
        action='$DC $_DINCFLAGS $_DVERFLAGS $_DDEBUGFLAGS $_DFLAGS -o $TARGET $DLINKFLAGS $__DRPATH $SOURCES $_DLIBDIRFLAGS $_DLIBFLAGS',
        emitter=DCommon.allAtOnceEmitter,
    )


def exists(env):
    return env.Detect('gdc')


# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/gettext_tool.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""gettext tool"""

import sys
import os

import SCons.Tool
import SCons.Warnings
from SCons.Errors import StopError
from SCons.Platform.mingw import MINGW_DEFAULT_PATHS
from SCons.Platform.cygwin import CYGWIN_DEFAULT_PATHS
from SCons.Tool.GettextCommon import (
    _msgfmt_exists,
    _msginit_exists,
    _msgmerge_exists,
    tool_list,
    _translate,
    _xgettext_exists,
)

def generate(env, **kw):
    for t in tool_list(env['PLATFORM'], env):
        if sys.platform == 'win32':
            tool = SCons.Tool.find_program_path(
                env, t, default_paths=MINGW_DEFAULT_PATHS + CYGWIN_DEFAULT_PATHS
            )
            if tool:
                tool_bin_dir = os.path.dirname(tool)
                env.AppendENVPath('PATH', tool_bin_dir)
            else:
                SCons.Warnings.warn(
                    SCons.Warnings.SConsWarning,
                    t + ' tool requested, but binary not found in ENV PATH',
                )
        env.Tool(t)
    env.AddMethod(_translate, 'Translate')

def exists(env):
    try:
        return (
            _xgettext_exists(env)
            and _msginit_exists(env)
            and _msgmerge_exists(env)
            and _msgfmt_exists(env)
        )
    except StopError:
        return False


# SCons/Tool/gfortran.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""

Tool-specific initialization for gfortran, the GNU Fortran 95/Fortran
2003 compiler.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""


import SCons.Util

from . import fortran


def generate(env):
    """Add Builders and construction variables for gfortran to an
    Environment."""
    fortran.generate(env)

    for dialect in ['F77', 'F90', 'FORTRAN', 'F95', 'F03', 'F08']:
        env['%s' % dialect] = 'gfortran'
        env['SH%s' % dialect] = '$%s' % dialect
        if env['PLATFORM'] in ['cygwin', 'win32']:
            env['SH%sFLAGS' % dialect] = SCons.Util.CLVar('$%sFLAGS' % dialect)
        else:
            env['SH%sFLAGS' % dialect] = SCons.Util.CLVar('$%sFLAGS -fPIC' % dialect)

        env['INC%sPREFIX' % dialect] = "-I"
        env['INC%sSUFFIX' % dialect] = ""

    env['FORTRANMODDIRPREFIX'] = "-J"


def exists(env):
    return env.Detect('gfortran')

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/gnulink.py

#
# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
"""SCons.Tool.gnulink

Tool-specific initialization for the gnu linker.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

import SCons.Tool.linkCommon
import SCons.Util
import SCons.Tool

from . import link


def generate(env):
    """Add Builders and construction variables for gnulink to an Environment."""
    link.generate(env)

    if env['PLATFORM'] == 'hpux':
        env['SHLINKFLAGS'] = SCons.Util.CLVar('$LINKFLAGS -shared -fPIC')

    # __RPATH is set to $_RPATH in the platform specification if that
    # platform supports it.
    env['RPATHPREFIX'] = '-Wl,-rpath='
    env['RPATHSUFFIX'] = ''
    env['_RPATH'] = '${_concat(RPATHPREFIX, RPATH, RPATHSUFFIX, __env__)}'


def exists(env):
    # TODO: sync with link.smart_link() to choose a linker
    linkers = {'CXX': ['g++'], 'CC': ['gcc']}
    alltools = []
    for langvar, linktools in linkers.items():
        if langvar in env:  # use CC over CXX when user specified CC but not CXX
            return SCons.Tool.FindTool(linktools, env)
        alltools.extend(linktools)
    return SCons.Tool.FindTool(alltools, env)  # find CXX or CC

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/gs.py

"""SCons.Tool.gs

Tool-specific initialization for Ghostscript.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import SCons.Action
import SCons.Builder
import SCons.Platform
import SCons.Util

# Ghostscript goes by different names on different platforms...
platform = SCons.Platform.platform_default()

if platform == 'os2':
    gs = 'gsos2'
elif platform == 'win32':
    gs = 'gswin32c'
else:
    gs = 'gs'

GhostscriptAction = None

def generate(env):
    """Add Builders and construction variables for Ghostscript to an
    Environment."""
    global GhostscriptAction
    # The following try-except block enables us to use the Tool
    # in standalone mode (without the accompanying pdf.py),
    # whenever we need an explicit call of gs via the Gs()
    # Builder ...
    try:
        if GhostscriptAction is None:
            GhostscriptAction = SCons.Action.Action('$GSCOM', '$GSCOMSTR')
    
        from SCons.Tool import pdf
        pdf.generate(env)
    
        bld = env['BUILDERS']['PDF']
        bld.add_action('.ps', GhostscriptAction)
    except ImportError as e:
        pass

    gsbuilder = SCons.Builder.Builder(action = SCons.Action.Action('$GSCOM', '$GSCOMSTR'))
    env['BUILDERS']['Gs'] = gsbuilder
    
    env['GS']      = gs
    env['GSFLAGS'] = SCons.Util.CLVar('-dNOPAUSE -dBATCH -sDEVICE=pdfwrite')
    env['GSCOM']   = '$GS $GSFLAGS -sOutputFile=$TARGET $SOURCES'


def exists(env):
    if 'PS2PDF' in env:
        return env.Detect(env['PS2PDF'])
    else:
        return env.Detect(gs) or SCons.Util.WhereIs(gs)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/gxx.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""SCons.Tool.g++

Tool-specific initialization for g++.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""


import SCons.Tool
import SCons.Util

from . import gcc
from . import cxx

compilers = ['g++']


def generate(env):
    """Add Builders and construction variables for g++ to an Environment."""
    static_obj, shared_obj = SCons.Tool.createObjBuilders(env)

    if 'CXX' not in env:
        env['CXX'] = env.Detect(compilers) or compilers[0]

    cxx.generate(env)

    # platform specific settings
    if env['PLATFORM'] == 'aix':
        env['SHCXXFLAGS'] = SCons.Util.CLVar('$CXXFLAGS -mminimal-toc')
        env['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 1
        env['SHOBJSUFFIX'] = '$OBJSUFFIX'
    elif env['PLATFORM'] == 'hpux':
        env['SHOBJSUFFIX'] = '.pic.o'
    elif env['PLATFORM'] == 'sunos':
        env['SHOBJSUFFIX'] = '.pic.o'
    # determine compiler version
    version = gcc.detect_version(env, env['CXX'])
    if version:
        env['CXXVERSION'] = version

    env['CCDEPFLAGS'] = '-MMD -MF ${TARGET}.d'
    env["NINJA_DEPFILE_PARSE_FORMAT"] = 'gcc'



def exists(env):
    # is executable, and is a GNU compiler (or accepts '--version' at least)
    return gcc.detect_version(env, env.Detect(env.get('CXX', compilers)))

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/hpc++.py

"""SCons.Tool.hpc++

Tool-specific initialization for c++ on HP/UX.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"


#forward proxy to the preffered cxx version
from SCons.Tool.hpcxx import *


# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/hpcc.py

"""SCons.Tool.hpcc

Tool-specific initialization for HP aCC and cc.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import SCons.Util

from . import cc

def generate(env):
    """Add Builders and construction variables for aCC & cc to an Environment."""
    cc.generate(env)

    env['CXX']        = 'aCC'
    env['SHCCFLAGS']  = SCons.Util.CLVar('$CCFLAGS +Z')

def exists(env):
    return env.Detect('aCC')

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/hpcxx.py

"""SCons.Tool.hpc++

Tool-specific initialization for c++ on HP/UX.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import os.path

import SCons.Util

import SCons.Tool.cxx
cplusplus = SCons.Tool.cxx
#cplusplus = __import__('cxx', globals(), locals(), [])


acc = None

# search for the acc compiler and linker front end

try:
    dirs = os.listdir('/opt')
except (IOError, OSError):
    # Not being able to read the directory because it doesn't exist
    # (IOError) or isn't readable (OSError) is okay.
    dirs = []

for dir in dirs:
    cc = '/opt/' + dir + '/bin/aCC'
    if os.path.exists(cc):
        acc = cc
        break

        
def generate(env):
    """Add Builders and construction variables for g++ to an Environment."""
    cplusplus.generate(env)

    if acc:
        env['CXX']        = acc or 'aCC'
        env['SHCXXFLAGS'] = SCons.Util.CLVar('$CXXFLAGS +Z')
        # determine version of aCC
        with os.popen(acc + ' -V 2>&1') as p:
            line = p.readline().rstrip()
        if line.find('aCC: HP ANSI C++') == 0:
            env['CXXVERSION'] = line.split()[-1]

        if env['PLATFORM'] == 'cygwin':
            env['SHCXXFLAGS'] = SCons.Util.CLVar('$CXXFLAGS')
        else:
            env['SHCXXFLAGS'] = SCons.Util.CLVar('$CXXFLAGS +Z')

def exists(env):
    return acc

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/hplink.py

"""SCons.Tool.hplink

Tool-specific initialization for the HP linker.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""

#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

import os.path

import SCons.Util

from . import link

ccLinker = None

# search for the acc compiler and linker front end

try:
    dirs = os.listdir('/opt')
except (IOError, OSError):
    # Not being able to read the directory because it doesn't exist
    # (IOError) or isn't readable (OSError) is okay.
    dirs = []

for dir in dirs:
    linker = '/opt/' + dir + '/bin/aCC'
    if os.path.exists(linker):
        ccLinker = linker
        break

def generate(env):
    """
    Add Builders and construction variables for Visual Age linker to
    an Environment.
    """
    link.generate(env)
    
    env['LINKFLAGS']   = SCons.Util.CLVar('-Wl,+s -Wl,+vnocompatwarnings')
    env['SHLINKFLAGS'] = SCons.Util.CLVar('$LINKFLAGS -b')
    env['SHLIBSUFFIX'] = '.sl'

def exists(env):
    return ccLinker

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/icc.py

"""SCons.Tool.icc

Tool-specific initialization for the OS/2 icc compiler.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

from . import cc

def generate(env):
    """Add Builders and construction variables for the OS/2 to an Environment."""
    cc.generate(env)

    env['CC']         = 'icc'
    env['CCCOM']      = '$CC $CFLAGS $CCFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS /c $SOURCES /Fo$TARGET'
    env['CXXCOM']     = '$CXX $CXXFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS /c $SOURCES /Fo$TARGET'
    env['CPPDEFPREFIX']  = '/D'
    env['CPPDEFSUFFIX']  = ''
    env['INCPREFIX']  = '/I'
    env['INCSUFFIX']  = ''
    env['CFILESUFFIX'] = '.c'
    env['CXXFILESUFFIX'] = '.cc'

def exists(env):
    return env.Detect('icc')

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/icl.py

"""SCons.Tool.icl

Tool-specific initialization for the Intel C/C++ compiler.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import SCons.Tool.intelc

# This has been completely superseded by intelc.py, which can
# handle both Windows and Linux versions.

def generate(*args, **kw):
    """Add Builders and construction variables for icl to an Environment."""
    return SCons.Tool.intelc.generate(*args, **kw)

def exists(*args, **kw):
    return SCons.Tool.intelc.exists(*args, **kw)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/ifl.py

"""SCons.Tool.ifl

Tool-specific initialization for the Intel Fortran compiler.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import SCons.Defaults
from SCons.Scanner.Fortran import FortranScan
from .FortranCommon import add_all_to_env

def generate(env):
    """Add Builders and construction variables for ifl to an Environment."""
    fscan = FortranScan("FORTRANPATH")
    SCons.Tool.SourceFileScanner.add_scanner('.i', fscan)
    SCons.Tool.SourceFileScanner.add_scanner('.i90', fscan)

    if 'FORTRANFILESUFFIXES' not in env:
        env['FORTRANFILESUFFIXES'] = ['.i']
    else:
        env['FORTRANFILESUFFIXES'].append('.i')

    if 'F90FILESUFFIXES' not in env:
        env['F90FILESUFFIXES'] = ['.i90']
    else:
        env['F90FILESUFFIXES'].append('.i90')

    add_all_to_env(env)

    env['FORTRAN']        = 'ifl'
    env['SHFORTRAN']      = '$FORTRAN'
    env['FORTRANCOM']     = '$FORTRAN $FORTRANFLAGS $_FORTRANINCFLAGS /c $SOURCES /Fo$TARGET'
    env['FORTRANPPCOM']   = '$FORTRAN $FORTRANFLAGS $CPPFLAGS $_CPPDEFFLAGS $_FORTRANINCFLAGS /c $SOURCES /Fo$TARGET'
    env['SHFORTRANCOM']   = '$SHFORTRAN $SHFORTRANFLAGS $_FORTRANINCFLAGS /c $SOURCES /Fo$TARGET'
    env['SHFORTRANPPCOM'] = '$SHFORTRAN $SHFORTRANFLAGS $CPPFLAGS $_CPPDEFFLAGS $_FORTRANINCFLAGS /c $SOURCES /Fo$TARGET'

def exists(env):
    return env.Detect('ifl')

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/ifort.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""

Tool-specific initialization for newer versions of the Intel Fortran Compiler
for Linux/Windows (and possibly Mac OS X).

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""


import SCons.Defaults
from SCons.Scanner.Fortran import FortranScan
from .FortranCommon import add_all_to_env


def generate(env):
    """Add Builders and construction variables for ifort to an Environment."""
    # ifort supports Fortran 90 and Fortran 95
    # Additionally, ifort recognizes more file extensions.
    fscan = FortranScan("FORTRANPATH")
    SCons.Tool.SourceFileScanner.add_scanner('.i', fscan)
    SCons.Tool.SourceFileScanner.add_scanner('.i90', fscan)

    if 'FORTRANFILESUFFIXES' not in env:
        env['FORTRANFILESUFFIXES'] = ['.i']
    else:
        env['FORTRANFILESUFFIXES'].append('.i')

    if 'F90FILESUFFIXES' not in env:
        env['F90FILESUFFIXES'] = ['.i90']
    else:
        env['F90FILESUFFIXES'].append('.i90')

    add_all_to_env(env)

    fc = 'ifort'

    for dialect in ['F77', 'F90', 'FORTRAN', 'F95']:
        env['%s' % dialect] = fc
        env['SH%s' % dialect] = '$%s' % dialect
        if env['PLATFORM'] == 'posix':
            env['SH%sFLAGS' % dialect] = SCons.Util.CLVar('$%sFLAGS -fPIC' % dialect)

    if env['PLATFORM'] == 'win32':
        # On Windows, the ifort compiler specifies the object on the
        # command line with -object:, not -o.  Massage the necessary
        # command-line construction variables.
        for dialect in ['F77', 'F90', 'FORTRAN', 'F95']:
            for var in ['%sCOM' % dialect, '%sPPCOM' % dialect,
                        'SH%sCOM' % dialect, 'SH%sPPCOM' % dialect]:
                env[var] = env[var].replace('-o $TARGET', '-object:$TARGET')
        env['FORTRANMODDIRPREFIX'] = "/module:"
    else:
        env['FORTRANMODDIRPREFIX'] = "-module "


def exists(env):
    return env.Detect('ifort')

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/ilink.py

"""SCons.Tool.ilink

Tool-specific initialization for the OS/2 ilink linker.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

import SCons.Defaults
import SCons.Tool
import SCons.Util

def generate(env):
    """Add Builders and construction variables for ilink to an Environment."""
    SCons.Tool.createProgBuilder(env)
    
    env['LINK']        = 'ilink'
    env['LINKFLAGS']   = SCons.Util.CLVar('')
    env['LINKCOM']     = '$LINK $LINKFLAGS /O:$TARGET $SOURCES $_LIBDIRFLAGS $_LIBFLAGS'
    env['LIBDIRPREFIX']='/LIBPATH:'
    env['LIBDIRSUFFIX']=''
    env['LIBLINKPREFIX']=''
    env['LIBLINKSUFFIX']='$LIBSUFFIX'

def exists(env):
    return env.Detect('ilink')

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/ilink32.py

"""SCons.Tool.ilink32

XXX

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import SCons.Tool
import SCons.Tool.bcc32
import SCons.Util

def generate(env):
    """Add Builders and construction variables for Borland ilink to an
    Environment."""
    SCons.Tool.createSharedLibBuilder(env)
    SCons.Tool.createProgBuilder(env)

    env['LINK']        = '$CC'
    env['LINKFLAGS']   = SCons.Util.CLVar('')
    env['LINKCOM']     = '$LINK -q $LINKFLAGS -e$TARGET $SOURCES $LIBS'
    env['LIBDIRPREFIX']=''
    env['LIBDIRSUFFIX']=''
    env['LIBLINKPREFIX']=''
    env['LIBLINKSUFFIX']='$LIBSUFFIX'


def exists(env):
    # Uses bcc32 to do linking as it generally knows where the standard
    # LIBS are and set up the linking correctly
    return SCons.Tool.bcc32.findIt('bcc32', env)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/install.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

""" Tool-specific initialization for the install tool.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""

import os
import stat
from shutil import copy2, copystat

import SCons.Action
import SCons.Tool
import SCons.Util
from SCons.Subst import SUBST_RAW
from SCons.Tool.linkCommon import (
    StringizeLibSymlinks,
    CreateLibSymlinks,
    EmitLibSymlinks,
)

# We keep track of *all* installed files.
_INSTALLED_FILES = []
_UNIQUE_INSTALLED_FILES = None

class CopytreeError(OSError):
    pass


def scons_copytree(src, dst, symlinks=False, ignore=None, copy_function=copy2,
                   ignore_dangling_symlinks=False, dirs_exist_ok=False):
    """Recursively copy a directory tree, SCons version.

    This is a modified copy of the Python 3.7 shutil.copytree function.
    SCons update: dirs_exist_ok dictates whether to raise an
    exception in case dst or any missing parent directory already
    exists. Implementation depends on os.makedirs having a similar
    flag, which it has since Python 3.2.  This version also raises an
    SCons-defined exception rather than the one defined locally to shtuil.
    This version uses a change from Python 3.8.
    TODO: we can remove this forked copy once the minimum Py version is 3.8.

    If exception(s) occur, an Error is raised with a list of reasons.

    If the optional symlinks flag is true, symbolic links in the
    source tree result in symbolic links in the destination tree; if
    it is false, the contents of the files pointed to by symbolic
    links are copied. If the file pointed by the symlink doesn't
    exist, an exception will be added in the list of errors raised in
    an Error exception at the end of the copy process.

    You can set the optional ignore_dangling_symlinks flag to true if you
    want to silence this exception. Notice that this has no effect on
    platforms that don't support os.symlink.

    The optional ignore argument is a callable. If given, it
    is called with the `src` parameter, which is the directory
    being visited by copytree(), and `names` which is the list of
    `src` contents, as returned by os.listdir():

        callable(src, names) -> ignored_names

    Since copytree() is called recursively, the callable will be
    called once for each directory that is copied. It returns a
    list of names relative to the `src` directory that should
    not be copied.

    The optional copy_function argument is a callable that will be used
    to copy each file. It will be called with the source path and the
    destination path as arguments. By default, copy2() is used, but any
    function that supports the same signature (like copy()) can be used.

    """
    names = os.listdir(src)
    if ignore is not None:
        ignored_names = ignore(src, names)
    else:
        ignored_names = set()

    os.makedirs(dst, exist_ok=dirs_exist_ok)
    errors = []
    for name in names:
        if name in ignored_names:
            continue
        srcname = os.path.join(src, name)
        dstname = os.path.join(dst, name)
        try:
            if os.path.islink(srcname):
                linkto = os.readlink(srcname)
                if symlinks:
                    # We can't just leave it to `copy_function` because legacy
                    # code with a custom `copy_function` may rely on copytree
                    # doing the right thing.
                    os.symlink(linkto, dstname)
                    copystat(srcname, dstname, follow_symlinks=not symlinks)
                else:
                    # ignore dangling symlink if the flag is on
                    if not os.path.exists(linkto) and ignore_dangling_symlinks:
                        continue
                    # otherwise let the copy occurs. copy2 will raise an error
                    if os.path.isdir(srcname):
                        scons_copytree(srcname, dstname, symlinks=symlinks,
                                       ignore=ignore, copy_function=copy_function,
                                       ignore_dangling_symlinks=ignore_dangling_symlinks,
                                       dirs_exist_ok=dirs_exist_ok)
                    else:
                        copy_function(srcname, dstname)
            elif os.path.isdir(srcname):
                scons_copytree(srcname, dstname, symlinks=symlinks,
                               ignore=ignore, copy_function=copy_function,
                               ignore_dangling_symlinks=ignore_dangling_symlinks,
                               dirs_exist_ok=dirs_exist_ok)
            else:
                # Will raise a SpecialFileError for unsupported file types
                copy_function(srcname, dstname)
        # catch the Error from the recursive copytree so that we can
        # continue with other files
        except CopytreeError as err:  # SCons change
            errors.extend(err.args[0])
        except OSError as why:
            errors.append((srcname, dstname, str(why)))
    try:
        copystat(src, dst)
    except OSError as why:
        # Copying file access times may fail on Windows
        if getattr(why, 'winerror', None) is None:
            errors.append((src, dst, str(why)))
    if errors:
        raise CopytreeError(errors)  # SCons change
    return dst

#
# Functions doing the actual work of the Install Builder.
#
def copyFunc(dest, source, env) -> int:
    """Install a source file or directory into a destination by copying.

    Mode/permissions bits will be copied as well, except that the target
    will be made writable.

    Returns:
        POSIX-style error code - 0 for success, non-zero for fail
    """
    if os.path.isdir(source):
        if os.path.exists(dest):
            if not os.path.isdir(dest):
                raise SCons.Errors.UserError("cannot overwrite non-directory `%s' with a directory `%s'" % (str(dest), str(source)))
        else:
            parent = os.path.split(dest)[0]
            if not os.path.exists(parent):
                os.makedirs(parent)
        scons_copytree(source, dest, dirs_exist_ok=True)
    else:
        copy2(source, dest)
        st = os.stat(source)
        os.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)

    return 0

#
# Functions doing the actual work of the InstallVersionedLib Builder.
#
def copyFuncVersionedLib(dest, source, env) -> int:
    """Install a versioned library into a destination by copying.

    Any required symbolic links for other library names are created.

    Mode/permissions bits will be copied as well, except that the target
    will be made writable.

    Returns:
        POSIX-style error code - 0 for success, non-zero for fail
    """
    if os.path.isdir(source):
        raise SCons.Errors.UserError("cannot install directory `%s' as a version library" % str(source) )
    else:
        # remove the link if it is already there
        try:
            os.remove(dest)
        except:
            pass
        copy2(source, dest)
        st = os.stat(source)
        os.chmod(dest, stat.S_IMODE(st[stat.ST_MODE]) | stat.S_IWRITE)
        installShlibLinks(dest, source, env)

    return 0

def listShlibLinksToInstall(dest, source, env):
    install_links = []
    source = env.arg2nodes(source)
    dest = env.fs.File(dest)
    install_dir = dest.get_dir()
    for src in source:
        symlinks = getattr(getattr(src, 'attributes', None), 'shliblinks', None)
        if symlinks:
            for link, linktgt in symlinks:
                link_base = os.path.basename(link.get_path())
                linktgt_base = os.path.basename(linktgt.get_path())
                install_link = env.fs.File(link_base, install_dir)
                install_linktgt = env.fs.File(linktgt_base, install_dir)
                install_links.append((install_link, install_linktgt))
    return install_links

def installShlibLinks(dest, source, env):
    """If we are installing a versioned shared library create the required links."""
    Verbose = False
    symlinks = listShlibLinksToInstall(dest, source, env)
    if Verbose:
        print('installShlibLinks: symlinks={!r}'.format(StringizeLibSymlinks(symlinks)))
    if symlinks:
        CreateLibSymlinks(env, symlinks)
    return

def installFunc(target, source, env) -> int:
    """Install a source file into a target.

    Uses the function specified in the INSTALL construction variable.

    Returns:
        POSIX-style error code - 0 for success, non-zero for fail
    """

    try:
        install = env['INSTALL']
    except KeyError:
        raise SCons.Errors.UserError('Missing INSTALL construction variable.')

    assert len(target) == len(source), (
        "Installing source %s into target %s: "
        "target and source lists must have same length."
        % (list(map(str, source)), list(map(str, target)))
    )
    for t, s in zip(target, source):
        if install(t.get_path(), s.get_path(), env):
            return 1

    return 0

def installFuncVersionedLib(target, source, env) -> int:
    """Install a versioned library into a target.

    Uses the function specified in the INSTALL construction variable.

    Returns:
        POSIX-style error code - 0 for success, non-zero for fail
    """

    try:
        install = env['INSTALLVERSIONEDLIB']
    except KeyError:
        raise SCons.Errors.UserError(
            'Missing INSTALLVERSIONEDLIB construction variable.'
        )

    assert len(target) == len(source), (
        "Installing source %s into target %s: "
        "target and source lists must have same length."
        % (list(map(str, source)), list(map(str, target)))
    )
    for t, s in zip(target, source):
        if hasattr(t.attributes, 'shlibname'):
            tpath = os.path.join(t.get_dir(), t.attributes.shlibname)
        else:
            tpath = t.get_path()
        if install(tpath, s.get_path(), env):
            return 1

    return 0

def stringFunc(target, source, env):
    installstr = env.get('INSTALLSTR')
    if installstr:
        return env.subst_target_source(installstr, SUBST_RAW, target, source)
    target = str(target[0])
    source = str(source[0])
    if os.path.isdir(source):
        type = 'directory'
    else:
        type = 'file'
    return 'Install %s: "%s" as "%s"' % (type, source, target)

#
# Emitter functions
#
def add_targets_to_INSTALLED_FILES(target, source, env):
    """ An emitter that adds all target files to the list stored in the
    _INSTALLED_FILES global variable. This way all installed files of one
    scons call will be collected.
    """
    global _INSTALLED_FILES, _UNIQUE_INSTALLED_FILES
    _INSTALLED_FILES.extend(target)

    _UNIQUE_INSTALLED_FILES = None
    return (target, source)

def add_versioned_targets_to_INSTALLED_FILES(target, source, env):
    """ An emitter that adds all target files to the list stored in the
    _INSTALLED_FILES global variable. This way all installed files of one
    scons call will be collected.
    """
    global _INSTALLED_FILES, _UNIQUE_INSTALLED_FILES
    Verbose = False
    _INSTALLED_FILES.extend(target)
    if Verbose:
        print("add_versioned_targets_to_INSTALLED_FILES: target={!r}".format(list(map(str, target))))
    symlinks = listShlibLinksToInstall(target[0], source, env)
    if symlinks:
        EmitLibSymlinks(env, symlinks, target[0])
    _UNIQUE_INSTALLED_FILES = None
    return (target, source)

class DESTDIR_factory:
    """ A node factory, where all files will be relative to the dir supplied
    in the constructor.
    """
    def __init__(self, env, dir):
        self.env = env
        self.dir = env.arg2nodes( dir, env.fs.Dir )[0]

    def Entry(self, name):
        name = SCons.Util.make_path_relative(name)
        return self.dir.Entry(name)

    def Dir(self, name):
        name = SCons.Util.make_path_relative(name)
        return self.dir.Dir(name)

#
# The Builder Definition
#
install_action       = SCons.Action.Action(installFunc, stringFunc)
installas_action     = SCons.Action.Action(installFunc, stringFunc)
installVerLib_action = SCons.Action.Action(installFuncVersionedLib, stringFunc)

BaseInstallBuilder               = None

def InstallBuilderWrapper(env, target=None, source=None, dir=None, **kw):
    if target and dir:
        import SCons.Errors
        raise SCons.Errors.UserError("Both target and dir defined for Install(), only one may be defined.")
    if not dir:
        dir=target

    import SCons.Script
    install_sandbox = SCons.Script.GetOption('install_sandbox')
    if install_sandbox:
        target_factory = DESTDIR_factory(env, install_sandbox)
    else:
        target_factory = env.fs

    try:
        dnodes = env.arg2nodes(dir, target_factory.Dir)
    except TypeError:
        raise SCons.Errors.UserError("Target `%s' of Install() is a file, but should be a directory.  Perhaps you have the Install() arguments backwards?" % str(dir))
    sources = env.arg2nodes(source, env.fs.Entry)
    tgt = []
    for dnode in dnodes:
        for src in sources:
            # Prepend './' so the lookup doesn't interpret an initial
            # '#' on the file name portion as meaning the Node should
            # be relative to the top-level SConstruct directory.
            target = env.fs.Entry('.'+os.sep+src.name, dnode)
            tgt.extend(BaseInstallBuilder(env, target, src, **kw))
    return tgt


def InstallAsBuilderWrapper(env, target=None, source=None, **kw):
    result = []
    for src, tgt in map(lambda x, y: (x, y), source, target):
        result.extend(BaseInstallBuilder(env, tgt, src, **kw))
    return result

BaseVersionedInstallBuilder = None


def InstallVersionedBuilderWrapper(env, target=None, source=None, dir=None, **kw):
    if target and dir:
        import SCons.Errors
        raise SCons.Errors.UserError("Both target and dir defined for Install(), only one may be defined.")
    if not dir:
        dir=target

    import SCons.Script
    install_sandbox = SCons.Script.GetOption('install_sandbox')
    if install_sandbox:
        target_factory = DESTDIR_factory(env, install_sandbox)
    else:
        target_factory = env.fs

    try:
        dnodes = env.arg2nodes(dir, target_factory.Dir)
    except TypeError:
        raise SCons.Errors.UserError("Target `%s' of Install() is a file, but should be a directory.  Perhaps you have the Install() arguments backwards?" % str(dir))
    sources = env.arg2nodes(source, env.fs.Entry)
    tgt = []
    for dnode in dnodes:
        for src in sources:
            # Prepend './' so the lookup doesn't interpret an initial
            # '#' on the file name portion as meaning the Node should
            # be relative to the top-level SConstruct directory.
            target = env.fs.Entry('.'+os.sep+src.name, dnode)
            tgt.extend(BaseVersionedInstallBuilder(env, target, src, **kw))
    return tgt

added = None


def generate(env):

    from SCons.Script import AddOption, GetOption
    global added
    if not added:
        added = 1
        AddOption('--install-sandbox',
                  dest='install_sandbox',
                  type="string",
                  action="store",
                  help='A directory under which all installed files will be placed.')

    global BaseInstallBuilder
    if BaseInstallBuilder is None:
        install_sandbox = GetOption('install_sandbox')
        if install_sandbox:
            target_factory = DESTDIR_factory(env, install_sandbox)
        else:
            target_factory = env.fs

        BaseInstallBuilder = SCons.Builder.Builder(
                              action         = install_action,
                              target_factory = target_factory.Entry,
                              source_factory = env.fs.Entry,
                              multi          = True,
                              emitter        = [ add_targets_to_INSTALLED_FILES, ],
                              source_scanner = SCons.Scanner.ScannerBase({}, name='Install', recursive=False),
                              name           = 'InstallBuilder')

    global BaseVersionedInstallBuilder
    if BaseVersionedInstallBuilder is None:
        install_sandbox = GetOption('install_sandbox')
        if install_sandbox:
            target_factory = DESTDIR_factory(env, install_sandbox)
        else:
            target_factory = env.fs

        BaseVersionedInstallBuilder = SCons.Builder.Builder(
                                       action         = installVerLib_action,
                                       target_factory = target_factory.Entry,
                                       source_factory = env.fs.Entry,
                                       multi          = True,
                                       emitter        = [ add_versioned_targets_to_INSTALLED_FILES, ],
                                       name           = 'InstallVersionedBuilder')

    env['BUILDERS']['_InternalInstall'] = InstallBuilderWrapper
    env['BUILDERS']['_InternalInstallAs'] = InstallAsBuilderWrapper
    env['BUILDERS']['_InternalInstallVersionedLib'] = InstallVersionedBuilderWrapper

    # We'd like to initialize this doing something like the following,
    # but there isn't yet support for a ${SOURCE.type} expansion that
    # will print "file" or "directory" depending on what's being
    # installed.  For now we punt by not initializing it, and letting
    # the stringFunc() that we put in the action fall back to the
    # hand-crafted default string if it's not set.
    #
    #try:
    #    env['INSTALLSTR']
    #except KeyError:
    #    env['INSTALLSTR'] = 'Install ${SOURCE.type}: "$SOURCES" as "$TARGETS"'

    try:
        env['INSTALL']
    except KeyError:
        env['INSTALL'] = copyFunc

    try:
        env['INSTALLVERSIONEDLIB']
    except KeyError:
        env['INSTALLVERSIONEDLIB'] = copyFuncVersionedLib

def exists(env):
    return 1

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/intelc.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""Tool-specific initialization for the Intel C/C++ compiler.

Supports Linux and Windows compilers, v7 and up.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""

import glob
import math
import os.path
import re
import sys

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.startswith('linux')
is_mac     = sys.platform == 'darwin'

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

# 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 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
    # XXX needs to be updated for 2011+ versions (like 2011.11.344 which is compiler v12.1.5)
    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, list(valid_abis.keys())))
    return abi

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. (Windows only)
    """
    # Open the key:
    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:
        # For version 13 and later, check UUID subkeys for valuename
        if is_win64:
            K = 'Software\\Wow6432Node\\Intel\\Suites\\' + version + "\\Defaults\\C++\\" + abi.upper()
        else:
            K = 'Software\\Intel\\Suites\\' + version + "\\Defaults\\C++\\" + abi.upper()
        try:
            k = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE, K)
            uuid = SCons.Util.RegQueryValueEx(k, 'SubKey')[0]

            if is_win64:
                K = 'Software\\Wow6432Node\\Intel\\Suites\\' + version + "\\" + uuid + "\\C++"
            else:
                K = 'Software\\Intel\\Suites\\' + version + "\\" + uuid + "\\C++"
            k = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE, K)

            try:
                v = SCons.Util.RegQueryValueEx(k, valuename)[0]
                return v  # or v.encode('iso-8859-1', 'replace') to remove unicode?
            except SCons.Util.RegError:
                if abi.upper() == 'EM64T':
                    abi = 'em64t_native'
                if is_win64:
                    K = 'Software\\Wow6432Node\\Intel\\Suites\\' + version + "\\" + uuid + "\\C++\\" + abi.upper()
                else:
                    K = 'Software\\Intel\\Suites\\' + version + "\\" + uuid + "\\C++\\" + abi.upper()
                k = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE, K)

            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 MissingRegistryError("%s was not found in the registry, for Intel compiler version %s, abi='%s'"%(K, version,abi))

        except (SCons.Util.RegError, OSError):
            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 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" or "9.0"
    with most recent compiler version first.
    """
    versions=[]
    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 OSError:
            # For version 13 or later, check for default instance UUID
            if is_win64:
                keyname = 'Software\\WoW6432Node\\Intel\\Suites'
            else:
                keyname = 'Software\\Intel\\Suites'
            try:
                k = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE,
                                            keyname)
            except OSError:
                return []
        i = 0
        versions = []
        try:
            while i < 100:
                subkey = SCons.Util.RegEnumKey(k, i) # raises SConsEnvironmentError
                # 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.
                if subkey == 'Defaults': # Ignore default instances
                    i = i + 1
                    continue
                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 as 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 or is_mac:
        for d in glob.glob('/opt/intel_cc_*'):
            # 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][0-9.]*)$', d)
            if m:
                versions.append(m.group(1))
        for d in glob.glob('/opt/intel/Compiler/*'):
            # Typical dir here is /opt/intel/Compiler/11.1
            m = re.search(r'([0-9][0-9.]*)$', d)
            if m:
                versions.append(m.group(1))
        for d in glob.glob('/opt/intel/composerxe-*'):
            # Typical dir here is /opt/intel/composerxe-2011.4.184
            m = re.search(r'([0-9][0-9.]*)$', d)
            if m:
                versions.append(m.group(1))
        for d in glob.glob('/opt/intel/composer_xe_*'):
            # Typical dir here is /opt/intel/composer_xe_2011_sp1.11.344
            # The _sp1 is useless, the installers are named 2011.9.x, 2011.10.x, 2011.11.x
            m = re.search(r'([0-9]{0,4})(?:_sp\d*)?\.([0-9][0-9.]*)$', d)
            if m:
                versions.append("%s.%s"%(m.group(1), m.group(2)))
        for d in glob.glob('/opt/intel/compilers_and_libraries_*'):
            # JPA: For the new version of Intel compiler 2016.1.
            m = re.search(r'([0-9]{0,4})(?:_sp\d*)?\.([0-9][0-9.]*)$', d)
            if m:
                versions.append("%s.%s"%(m.group(1), m.group(2)))

    def keyfunc(str):
        """Given a dot-separated version string, return a tuple of ints representing it."""
        return [int(x) for x in str.split('.')]
    # split into ints, sort, then remove dups
    return sorted(SCons.Util.unique(versions), key=keyfunc, reverse=True)

def get_intel_compiler_top(version, abi):
    """
    Return the main path to the top-level dir of the Intel compiler,
    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_windows:
        if not SCons.Util.can_read_reg:
            raise NoRegistryModuleError("No Windows registry module was found")
        top = get_intel_registry_value('ProductDir', version, abi)
        archdir={'x86_64': 'intel64',
                 'amd64' : 'intel64',
                 'em64t' : 'intel64',
                 'x86'   : 'ia32',
                 'i386'  : 'ia32',
                 'ia32'  : 'ia32'
        }[abi] # for v11 and greater
        # 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")) \
              and not os.path.exists(os.path.join(top, "Bin", archdir, "icl.exe")):
            raise MissingDirError("Can't find Intel compiler in %s" % top)
    elif is_mac or is_linux:
        def find_in_2008style_dir(version):
            # 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
            return top
        def find_in_2010style_dir(version):
            dirs=('/opt/intel/Compiler/%s/*'%version)
            # typically /opt/intel/Compiler/11.1/064 (then bin/intel64/icc)
            dirs=glob.glob(dirs)
            # find highest sub-version number by reverse sorting and picking first existing one.
            dirs.sort()
            dirs.reverse()
            top=None
            for d in dirs:
                if (os.path.exists(os.path.join(d, "bin", "ia32", "icc")) or
                    os.path.exists(os.path.join(d, "bin", "intel64", "icc"))):
                    top = d
                    break
            return top
        def find_in_2011style_dir(version):
            # The 2011 (compiler v12) dirs are inconsistent, so just redo the search from
            # get_all_compiler_versions and look for a match (search the newest form first)
            top=None
            for d in glob.glob('/opt/intel/composer_xe_*'):
                # Typical dir here is /opt/intel/composer_xe_2011_sp1.11.344
                # The _sp1 is useless, the installers are named 2011.9.x, 2011.10.x, 2011.11.x
                m = re.search(r'([0-9]{0,4})(?:_sp\d*)?\.([0-9][0-9.]*)$', d)
                if m:
                    cur_ver = "%s.%s"%(m.group(1), m.group(2))
                    if cur_ver == version and \
                        (os.path.exists(os.path.join(d, "bin", "ia32", "icc")) or
                        os.path.exists(os.path.join(d, "bin", "intel64", "icc"))):
                        top = d
                        break
            if not top:
                for d in glob.glob('/opt/intel/composerxe-*'):
                    # Typical dir here is /opt/intel/composerxe-2011.4.184
                    m = re.search(r'([0-9][0-9.]*)$', d)
                    if m and m.group(1) == version and \
                        (os.path.exists(os.path.join(d, "bin", "ia32", "icc")) or
                        os.path.exists(os.path.join(d, "bin", "intel64", "icc"))):
                            top = d
                            break
            return top
        def find_in_2016style_dir(version):
            # The 2016 (compiler v16) dirs are inconsistent from previous.
            top = None
            for d in glob.glob('/opt/intel/compilers_and_libraries_%s/linux'%version):
                if os.path.exists(os.path.join(d, "bin", "ia32", "icc")) or os.path.exists(os.path.join(d, "bin", "intel64", "icc")):
                    top = d
                    break
            return top

        top = find_in_2016style_dir(version) or find_in_2011style_dir(version) or find_in_2010style_dir(version) or find_in_2008style_dir(version)
        # print "INTELC: top=",top
        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=0):
    r"""Add Builders and construction variables for Intel C/C++ compiler
    to an Environment.

    Args:
      version (str): compiler version to use, like "80"
      abi (str): 'win32' or whatever Itanium version wants
      topdir (str): directory containing compiler tree, e.g.
        "c:\\Program Files\\Intel\\Compiler70".
        If `topdir` is used, `version` and `abi` are ignored.
      verbose: if >0, prints compiler version used.

    """
    if not (is_mac or is_linux or is_windows):
        # can't handle this platform
        return

    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:
        # 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:
        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, IntelCError):
            topdir = None

    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.SConsWarning):
            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:
        archdir={'x86_64': 'intel64',
                 'amd64' : 'intel64',
                 'em64t' : 'intel64',
                 'x86'   : 'ia32',
                 'i386'  : 'ia32',
                 'ia32'  : 'ia32'
        }[abi] # for v11 and greater
        if os.path.exists(os.path.join(topdir, 'bin', archdir)):
            bindir="bin/%s"%archdir
            libdir="lib/%s"%archdir
        else:
            bindir="bin"
            libdir="lib"
        if verbose:
            print("Intel C compiler: using version %s (%g), abi %s, in '%s/%s'"%\
                  (repr(version), linux_ver_normalize(version),abi,topdir,bindir))
            if is_linux:
                # Show the actual compiler version by running the compiler.
                os.system('%s/%s/icc --version'%(topdir,bindir))
            if is_mac:
                # Show the actual compiler version by running the compiler.
                os.system('%s/%s/icc --version'%(topdir,bindir))

        env['INTEL_C_COMPILER_TOP'] = topdir
        if is_linux:
            paths={'INCLUDE'         : 'include',
                   'LIB'             : libdir,
                   'PATH'            : bindir,
                   'LD_LIBRARY_PATH' : libdir}
            for p, v in paths.items():
                env.PrependENVPath(p, os.path.join(topdir, v))
        if is_mac:
            paths={'INCLUDE'         : 'include',
                   'LIB'             : libdir,
                   'PATH'            : bindir,
                   'LD_LIBRARY_PATH' : libdir}
            for p, v in paths.items():
                env.PrependENVPath(p, os.path.join(topdir, v))
        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)
                    # 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_windows:
        env['CC']        = 'icl'
        env['CXX']       = 'icl'
        env['LINK']      = 'xilink'
    else:
        env['CC']        = 'icc'
        env['CXX']       = 'icpc'
        # 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", '')
        K = r'SOFTWARE\Intel\Licenses'
        try:
            k = SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE, K)
            reglicdir = SCons.Util.RegQueryValueEx(k, "w_cpp")[0]
        except (AttributeError, SCons.Util.RegError):
            reglicdir = ""
        defaultlicdir = r'C:\Program Files\Common Files\Intel\Licenses'

        licdir = None
        for ld in [envlicdir, reglicdir]:
            # 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:
            licdir = defaultlicdir
            if not os.path.exists(licdir):
                class ICLLicenseDirWarning(SCons.Warnings.SConsWarning):
                    pass
                SCons.Warnings.enableWarningClass(ICLLicenseDirWarning)
                SCons.Warnings.warn(
                    ICLLicenseDirWarning,
                    "Intel license dir was not found.  "
                    "Tried using the INTEL_LICENSE_FILE environment variable "
                    "(%s), the registry (%s) and the default path (%s).  "
                    "Using the default path as a last resort."
                    % (envlicdir, reglicdir, defaultlicdir)
                )
        env['ENV']['INTEL_LICENSE_FILE'] = licdir

def exists(env):
    if not (is_mac or is_linux or is_windows):
        # can't handle this platform
        return 0

    try:
        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_windows:
            return env.Detect('icl')
        elif is_linux:
            return env.Detect('icc')
        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:


# SCons/Tool/ipkg.py

"""SCons.Tool.ipkg

Tool-specific initialization for ipkg.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

The ipkg tool calls the ipkg-build. Its only argument should be the 
packages fake_root.
"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import os

import SCons.Builder

def generate(env):
    """Add Builders and construction variables for ipkg to an Environment."""
    try:
        bld = env['BUILDERS']['Ipkg']
    except KeyError:
        bld = SCons.Builder.Builder(action='$IPKGCOM',
                                    suffix='$IPKGSUFFIX',
                                    source_scanner=None,
                                    target_scanner=None)
        env['BUILDERS']['Ipkg'] = bld


    env['IPKG'] = 'ipkg-build'
    env['IPKGCOM'] = '$IPKG $IPKGFLAGS ${SOURCE}'

    if env.WhereIs('id'):
        with os.popen('id -un') as p:
            env['IPKGUSER'] = p.read().strip()
        with os.popen('id -gn') as p:
            env['IPKGGROUP'] = p.read().strip()
    env['IPKGFLAGS'] = SCons.Util.CLVar('-o $IPKGUSER -g $IPKGGROUP')
    env['IPKGSUFFIX'] = '.ipk'

def exists(env):
    """
    Can we find the tool
    """
    return env.Detect('ipkg-build')

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/jar.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""Tool-specific initialization for jar.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""

import os
from typing import List

import SCons.Node
import SCons.Node.FS
import SCons.Subst
import SCons.Tool
import SCons.Util
import SCons.Warnings
from SCons.Node.FS import _my_normcase
from SCons.Tool.JavaCommon import get_java_install_dirs


def jarSources(target, source, env, for_signature) -> List[str]:
    """Only include sources that are not a manifest file."""
    try:
        env['JARCHDIR']
    except KeyError:
        jarchdir_set = False
    else:
        jarchdir_set = True
        jarchdir = env.subst('$JARCHDIR', target=target, source=source)
        if jarchdir:
            jarchdir = env.fs.Dir(jarchdir)
    result = []
    for src in source:
        contents = src.get_text_contents()
        if contents.startswith("Manifest-Version"):
            continue
        if jarchdir_set:
            _chdir = jarchdir
        else:
            try:
                _chdir = src.attributes.java_classdir
            except AttributeError:
                _chdir = None
        if _chdir:
            # If we are changing the dir with -C, then sources should
            # be relative to that directory.
            src = SCons.Subst.Literal(src.get_path(_chdir))
            result.append('-C')
            result.append(_chdir)
        result.append(src)
    return result

def jarManifest(target, source, env, for_signature):
    """Look in sources for a manifest file, if any."""
    for src in source:
        contents = src.get_text_contents()
        if contents.startswith("Manifest-Version"):
            return src
    return ''

def jarFlags(target, source, env, for_signature) -> str:
    """If we have a manifest, make sure that the 'm'
    flag is specified."""
    jarflags = env.subst('$JARFLAGS', target=target, source=source)
    for src in source:
        contents = src.get_text_contents()
        if contents.startswith("Manifest-Version"):
            if 'm' not in jarflags:
                return jarflags + 'm'
            break
    return jarflags

def Jar(env, target=None, source=[], *args, **kw):
    """The Jar Builder.

    This is a pseudo-Builder wrapper around the separate jar builders
    depending on whether the sources are a file list or a directory.
    """
    # TODO: W1113: Keyword argument before variable positional arguments list in the definition of Jar function
    # TODO: W0102: Dangerous default value [] as argument

    # jar target should not be a list so assume they passed
    # no target and want implicit target to be made and the arg
    # was actaully the list of sources
    if SCons.Util.is_List(target) and source == []:
        SCons.Warnings.warn(
            SCons.Warnings.SConsWarning,
            "Making implicit target jar file, and treating the list as sources"
        )
        source = target
        target = None

    # mutiple targets passed so build each target the same from the
    # same source
    #TODO Maybe this should only be done once, and the result copied
    #     for each target since it should result in the same?
    if SCons.Util.is_List(target) and SCons.Util.is_List(source):
        jars = []
        for single_target in target:
            jars += env.Jar(target=single_target, source=source, *args, **kw)
        return jars

    # they passed no target so make a target implicitly
    if target is None:
        try:
            # make target from the first source file
            target = os.path.splitext(str(source[0]))[0] + env.subst('$JARSUFFIX')
        except:
            # TODO: W0702: No exception type(s) specified
            # something strange is happening but attempt anyways
            SCons.Warnings.warn(
                SCons.Warnings.SConsWarning,
                "Could not make implicit target from sources, using directory"
            )
            target = os.path.basename(str(env.Dir('.'))) + env.subst('$JARSUFFIX')

    # make lists out of our target and sources
    if not SCons.Util.is_List(target):
        target = [target]
    if not SCons.Util.is_List(source):
        source = [source]

    # setup for checking through all the sources and handle accordingly
    java_class_suffix = env.subst('$JAVACLASSSUFFIX')
    java_suffix = env.subst('$JAVASUFFIX')
    target_nodes = []

    # function for determining what to do with a file and not a directory
    # if its already a class file then it can be used as a
    # source for jar, otherwise turn it into a class file then
    # return the source
    def file_to_class(src):
        if _my_normcase(str(src)).endswith(java_suffix):
            return env.JavaClassFile(source=src, *args, **kw)
        return [env.fs.File(src)]

    # function for calling the JavaClassDir builder if a directory is
    # passed as a source to Jar builder. The JavaClassDir builder will
    # return an empty list if there were no target classes built from
    # the directory, in this case assume the user wanted the directory
    # copied into the jar as is (it contains other files such as
    # resources or class files compiled from prior commands)
    # TODO: investigate the expexcted behavior for directories that
    #       have mixed content, such as Java files along side other files
    #       files.
    def dir_to_class(src):
        dir_targets = env.JavaClassDir(source=src, *args, **kw)
        if dir_targets == []:
            # no classes files could be built from the source dir
            # so pass the dir as is.
            return [env.fs.Dir(src)]
        return dir_targets

    # loop through the sources and handle each accordingly
    # the goal here is to get all the source files into a class
    # file or a directory that contains class files
    for src in SCons.Util.flatten(source):
        src = env.subst(src)
        if isinstance(src, SCons.Node.FS.Base):
            if isinstance(src, SCons.Node.FS.File):
                # found a file so make sure its a class file
                target_nodes.extend(file_to_class(src))
            else:
                # found a dir so get the class files out of it
                target_nodes.extend(dir_to_class(src))
        else:
            try:
                # source is string try to convert it to file
                target_nodes.extend(file_to_class(env.fs.File(src)))
                continue
            except:
                # TODO: W0702: No exception type(s) specified
                pass

            try:
                # source is string try to covnert it to dir
                target_nodes.extend(dir_to_class(env.fs.Dir(src)))
                continue
            except:
                # TODO: W0702: No exception type(s) specified
                pass

            SCons.Warnings.warn(
                SCons.Warnings.SConsWarning,
                ("File: " + str(src)
                + " could not be identified as File or Directory, skipping.")
            )

    # at this point all our sources have been converted to classes or
    # directories of class so pass it to the Jar builder
    return env.JarFile(target=target, source=target_nodes, *args, **kw)

def generate(env) -> None:
    """Add Builders and construction variables for jar to an Environment."""
    SCons.Tool.CreateJarBuilder(env)

    SCons.Tool.CreateJavaFileBuilder(env)
    SCons.Tool.CreateJavaClassFileBuilder(env)
    SCons.Tool.CreateJavaClassDirBuilder(env)

    env.AddMethod(Jar)

    if env['PLATFORM'] == 'win32':
        # Ensure that we have a proper path for jar
        paths = get_java_install_dirs('win32')
        jar = SCons.Tool.find_program_path(env, 'jar', default_paths=paths)
        if jar:
            jar_bin_dir = os.path.dirname(jar)
            env.AppendENVPath('PATH', jar_bin_dir)

    env['JAR'] = 'jar'
    env['JARFLAGS'] = SCons.Util.CLVar('cf')
    env['_JARFLAGS'] = jarFlags
    env['_JARMANIFEST'] = jarManifest
    env['_JARSOURCES'] = jarSources
    env['_JARCOM'] = '$JAR $_JARFLAGS $TARGET $_JARMANIFEST $_JARSOURCES'
    env['JARCOM'] = "${TEMPFILE('$_JARCOM','$JARCOMSTR')}"
    env['JARSUFFIX'] = '.jar'

def exists(env):
    # As reported by Jan Nijtmans in issue #2730, the simple
    #    return env.Detect('jar')
    # doesn't always work during initialization. For now, we
    # stop trying to detect an executable (analogous to the
    # javac Builder).
    # TODO: Come up with a proper detect() routine...and enable it.
    return True

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/javac.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""SCons.Tool.javac

Tool-specific initialization for javac.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""


import os
import os.path
from collections import OrderedDict

import SCons.Action
import SCons.Builder
from SCons.Node.FS import _my_normcase
from SCons.Tool.JavaCommon import parse_java_file, get_java_install_dirs, get_java_include_paths
import SCons.Util

def classname(path):
    """Turn a string (path name) into a Java class name."""
    return os.path.normpath(path).replace(os.sep, '.')

def emit_java_classes(target, source, env):
    """Create and return lists of source java files
    and their corresponding target class files.
    """
    java_suffix = env.get('JAVASUFFIX', '.java')
    class_suffix = env.get('JAVACLASSSUFFIX', '.class')

    target[0].must_be_same(SCons.Node.FS.Dir)
    classdir = target[0]

    s = source[0].rentry().disambiguate()
    if isinstance(s, SCons.Node.FS.File):
        sourcedir = s.dir.rdir()
    elif isinstance(s, SCons.Node.FS.Dir):
        sourcedir = s.rdir()
    else:
        raise SCons.Errors.UserError("Java source must be File or Dir, not '%s'" % s.__class__)

    slist = []
    js = _my_normcase(java_suffix)
    for entry in source:
        entry = entry.rentry().disambiguate()
        if isinstance(entry, SCons.Node.FS.File):
            slist.append(entry)
        elif isinstance(entry, SCons.Node.FS.Dir):
            result = OrderedDict()
            dirnode = entry.rdir()
            def find_java_files(arg, dirpath, filenames):
                java_files = sorted([n for n in filenames
                                       if _my_normcase(n).endswith(js)])
                mydir = dirnode.Dir(dirpath)
                java_paths = [mydir.File(f) for f in java_files]
                for jp in java_paths:
                     arg[jp] = True
            for dirpath, dirnames, filenames in os.walk(dirnode.get_abspath()):
               find_java_files(result, dirpath, filenames)
            entry.walk(find_java_files, result)

            slist.extend(list(result.keys()))
        else:
            raise SCons.Errors.UserError("Java source must be File or Dir, not '%s'" % entry.__class__)

    version = env.get('JAVAVERSION', '1.4')
    full_tlist = []
    for f in slist:
        tlist = []
        source_file_based = True
        pkg_dir = None
        if not f.is_derived():
            pkg_dir, classes = parse_java_file(f.rfile().get_abspath(), version)
            if classes:
                source_file_based = False
                if pkg_dir:
                    d = target[0].Dir(pkg_dir)
                    p = pkg_dir + os.sep
                else:
                    d = target[0]
                    p = ''
                for c in classes:
                    t = d.File(c + class_suffix)
                    t.attributes.java_classdir = classdir
                    t.attributes.java_sourcedir = sourcedir
                    t.attributes.java_classname = classname(p + c)
                    tlist.append(t)

        if source_file_based:
            base = f.name[:-len(java_suffix)]
            if pkg_dir:
                t = target[0].Dir(pkg_dir).File(base + class_suffix)
            else:
                t = target[0].File(base + class_suffix)
            t.attributes.java_classdir = classdir
            t.attributes.java_sourcedir = f.dir
            t.attributes.java_classname = classname(base)
            tlist.append(t)

        for t in tlist:
            t.set_specific_source([f])

        full_tlist.extend(tlist)

    return full_tlist, slist

JavaAction = SCons.Action.Action('$JAVACCOM', '$JAVACCOMSTR')

JavaBuilder = SCons.Builder.Builder(action = JavaAction,
                    emitter = emit_java_classes,
                    target_factory = SCons.Node.FS.Entry,
                    source_factory = SCons.Node.FS.Entry)

class pathopt:
    """
    Callable object for generating javac-style path options from
    a construction variable (e.g. -classpath, -sourcepath).
    """
    def __init__(self, opt, var, default=None):
        self.opt = opt
        self.var = var
        self.default = default

    def __call__(self, target, source, env, for_signature):
        path = env[self.var]
        if path and not SCons.Util.is_List(path):
            path = [path]
        if self.default:
            default = env[self.default]
            if default:
                if not SCons.Util.is_List(default):
                    default = [default]
                path = path + default
        if path:
            path = SCons.Util.flatten(path)
            return [self.opt, os.pathsep.join(map(str, path))]
        else:
            return []

def Java(env, target, source, *args, **kw):
    """
    A pseudo-Builder wrapper around the separate JavaClass{File,Dir}
    Builders.
    """
    if not SCons.Util.is_List(target):
        target = [target]
    if not SCons.Util.is_List(source):
        source = [source]

    # Pad the target list with repetitions of the last element in the
    # list so we have a target for every source element.
    target = target + ([target[-1]] * (len(source) - len(target)))

    java_suffix = env.subst('$JAVASUFFIX')
    result = []

    for t, s in zip(target, source):
        if isinstance(s, SCons.Node.FS.Base):
            if isinstance(s, SCons.Node.FS.File):
                b = env.JavaClassFile
            else:
                b = env.JavaClassDir
        else:
            if os.path.isfile(s):
                b = env.JavaClassFile
            elif os.path.isdir(s):
                b = env.JavaClassDir
            elif s[-len(java_suffix):] == java_suffix:
                b = env.JavaClassFile
            else:
                b = env.JavaClassDir
        result.extend(b(t, s, *args, **kw))

    return result

def generate(env):
    """Add Builders and construction variables for javac to an Environment."""
    java_file = SCons.Tool.CreateJavaFileBuilder(env)
    java_class = SCons.Tool.CreateJavaClassFileBuilder(env)
    java_class_dir = SCons.Tool.CreateJavaClassDirBuilder(env)
    java_class.add_emitter(None, emit_java_classes)
    java_class.add_emitter(env.subst('$JAVASUFFIX'), emit_java_classes)
    java_class_dir.emitter = emit_java_classes

    env.AddMethod(Java)

    version = env.get('JAVAVERSION', None)

    if env['PLATFORM'] == 'win32':
        # Ensure that we have a proper path for javac
        paths = get_java_install_dirs('win32', version=version)
        javac = SCons.Tool.find_program_path(env, 'javac', default_paths=paths)
        if javac:
            javac_bin_dir = os.path.dirname(javac)
            env.AppendENVPath('PATH', javac_bin_dir)
    else:
        javac = SCons.Tool.find_program_path(env, 'javac')


    env.SetDefault(
        JAVAC='javac',
        JAVACFLAGS=SCons.Util.CLVar(''),
        JAVAINCLUDES=get_java_include_paths(env, javac, version),
        JAVACLASSSUFFIX='.class',
        JAVASUFFIX='.java',
        JAVABOOTCLASSPATH=[],
        JAVACLASSPATH=[],
        JAVASOURCEPATH=[],
    )
    env['_javapathopt'] = pathopt
    env['_JAVABOOTCLASSPATH'] = '${_javapathopt("-bootclasspath", "JAVABOOTCLASSPATH")} '
    env['_JAVACLASSPATH'] = '${_javapathopt("-classpath", "JAVACLASSPATH")} '
    env['_JAVASOURCEPATH'] = '${_javapathopt("-sourcepath", "JAVASOURCEPATH", "_JAVASOURCEPATHDEFAULT")} '
    env['_JAVASOURCEPATHDEFAULT'] = '${TARGET.attributes.java_sourcedir}'
    env['_JAVACCOM'] = '$JAVAC $JAVACFLAGS $_JAVABOOTCLASSPATH $_JAVACLASSPATH -d ${TARGET.attributes.java_classdir} $_JAVASOURCEPATH $SOURCES'
    env['JAVACCOM'] = "${TEMPFILE('$_JAVACCOM','$JAVACCOMSTR')}"

def exists(env):
    return 1

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/javacTests.py

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

import os
import unittest

import SCons.Tool.javac

class DummyNode:
    def __init__(self, val):
        self.val = val

    def __str__(self):
        return str(self.val)

class pathoptTestCase(unittest.TestCase):
    def assert_pathopt(self, expect, path):
        popt = SCons.Tool.javac.pathopt('-foopath', 'FOOPATH')
        env = {'FOOPATH': path}
        actual = popt(None, None, env, None)
        self.assertEqual(expect, actual)

    def assert_pathopt_default(self, expect, path, default):
        popt = SCons.Tool.javac.pathopt('-foopath', 'FOOPATH', default='DPATH')
        env = {'FOOPATH': path,
               'DPATH': default}
        actual = popt(None, None, env, None)
        self.assertEqual(expect, actual)

    def test_unset(self):
        self.assert_pathopt([], None)
        self.assert_pathopt([], '')

    def test_str(self):
        self.assert_pathopt(['-foopath', '/foo/bar'],
                            '/foo/bar')

    def test_list_str(self):
        self.assert_pathopt(['-foopath', '/foo%s/bar' % os.pathsep],
                            ['/foo', '/bar'])

    def test_uses_pathsep(self):
        save = os.pathsep
        try:
            os.pathsep = '!'
            self.assert_pathopt(['-foopath', 'foo!bar'],
                                ['foo', 'bar'])
        finally:
            os.pathsep = save

    def test_node(self):
        self.assert_pathopt(['-foopath', '/foo'],
                            DummyNode('/foo'))

    def test_list_node(self):
        self.assert_pathopt(['-foopath', os.pathsep.join(['/foo','/bar'])],
                            ['/foo', DummyNode('/bar')])

    def test_default_str(self):
        self.assert_pathopt_default(
            ['-foopath', os.pathsep.join(['/foo','/bar','/baz'])],
            ['/foo', '/bar'],
            '/baz')

    def test_default_list(self):
        self.assert_pathopt_default(
            ['-foopath', os.pathsep.join(['/foo','/bar','/baz'])],
            ['/foo', '/bar'],
            ['/baz'])

    def test_default_unset(self):
        self.assert_pathopt_default(
            ['-foopath', '/foo'],
            '/foo',
            None)
        self.assert_pathopt_default(
            ['-foopath', '/foo'],
            '/foo',
            '')
    
    def test_list_within_list(self):
        self.assert_pathopt(['-foopath', os.pathsep.join(['/foo','/bar'])],
                    ['/foo', ['/bar']])


if __name__ == "__main__":
    unittest.main()


# SCons/Tool/javah.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""SCons.Tool.javah

Tool-specific initialization for javah.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""


import os.path

import SCons.Action
import SCons.Builder
import SCons.Node.FS
import SCons.Tool.javac
import SCons.Util
from SCons.Tool.JavaCommon import get_java_install_dirs


def emit_java_headers(target, source, env):
    """Create and return lists of Java stub header files that will
    be created from a set of class files.
    """
    class_suffix = env.get('JAVACLASSSUFFIX', '.class')
    classdir = env.get('JAVACLASSDIR')

    if not classdir:
        try:
            s = source[0]
        except IndexError:
            classdir = '.'
        else:
            try:
                classdir = s.attributes.java_classdir
            except AttributeError:
                classdir = '.'
    classdir = env.Dir(classdir).rdir()

    if str(classdir) == '.':
        c_ = None
    else:
        c_ = str(classdir) + os.sep

    slist = []
    for src in source:
        try:
            classname = src.attributes.java_classname
        except AttributeError:
            classname = str(src)
            if c_ and classname[:len(c_)] == c_:
                classname = classname[len(c_):]
            if class_suffix and classname[-len(class_suffix):] == class_suffix:
                classname = classname[:-len(class_suffix)]
            classname = SCons.Tool.javac.classname(classname)
        s = src.rfile()
        s.attributes.java_classname = classname
        slist.append(s)

    s = source[0].rfile()
    if not hasattr(s.attributes, 'java_classdir'):
        s.attributes.java_classdir = classdir

    if target[0].__class__ is SCons.Node.FS.File:
        tlist = target
    else:
        if not isinstance(target[0], SCons.Node.FS.Dir):
            target[0].__class__ = SCons.Node.FS.Dir
            target[0]._morph()
        tlist = []
        for s in source:
            fname = s.attributes.java_classname.replace('.', '_') + '.h'
            t = target[0].File(fname)
            t.attributes.java_lookupdir = target[0]
            tlist.append(t)

    return tlist, source

def JavaHOutFlagGenerator(target, source, env, for_signature):
    try:
        t = target[0]
    except (AttributeError, IndexError, TypeError):
        t = target
    try:
        return '-d ' + str(t.attributes.java_lookupdir)
    except AttributeError:
        return '-o ' + str(t)

def getJavaHClassPath(env,target, source, for_signature):
    path = "${SOURCE.attributes.java_classdir}"
    if 'JAVACLASSPATH' in env and env['JAVACLASSPATH']:
        path = SCons.Util.AppendPath(path, env['JAVACLASSPATH'])
    return "-classpath %s" % path

def generate(env):
    """Add Builders and construction variables for javah to an Environment."""
    java_javah = SCons.Tool.CreateJavaHBuilder(env)
    java_javah.emitter = emit_java_headers

    if env['PLATFORM'] == 'win32':
        # Ensure that we have a proper path for javah
        paths = get_java_install_dirs('win32')
        javah = SCons.Tool.find_program_path(env, 'javah', default_paths=paths)
        if javah:
            javah_bin_dir = os.path.dirname(javah)
            env.AppendENVPath('PATH', javah_bin_dir)

    env.SetDefault(
        JAVAH='javah',
        JAVAHFLAGS=SCons.Util.CLVar(''),
        JAVACLASSSUFFIX='.class',
        JAVASUFFIX='.java',
    )
    env['_JAVAHOUTFLAG'] = JavaHOutFlagGenerator
    env['_JAVAHCLASSPATH'] = getJavaHClassPath
    env['JAVAHCOM'] = '$JAVAH $JAVAHFLAGS $_JAVAHOUTFLAG $_JAVAHCLASSPATH ${SOURCES.attributes.java_classname}'

def exists(env):
    return env.Detect('javah')

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/latex.py

"""SCons.Tool.latex

Tool-specific initialization for LaTeX.
Generates .dvi files from .latex or .ltx files

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import SCons.Action
import SCons.Defaults
import SCons.Scanner.LaTeX
import SCons.Util
import SCons.Tool
import SCons.Tool.tex

def LaTeXAuxFunction(target = None, source= None, env=None):
    result = SCons.Tool.tex.InternalLaTeXAuxAction( SCons.Tool.tex.LaTeXAction, target, source, env )
    if result != 0:
        SCons.Tool.tex.check_file_error_message(env['LATEX'])
    return result

LaTeXAuxAction = SCons.Action.Action(LaTeXAuxFunction,
                              strfunction=SCons.Tool.tex.TeXLaTeXStrFunction)

def generate(env):
    """Add Builders and construction variables for LaTeX to an Environment."""

    env.AppendUnique(LATEXSUFFIXES=SCons.Tool.LaTeXSuffixes)

    from . import dvi
    dvi.generate(env)

    from . import pdf
    pdf.generate(env)

    bld = env['BUILDERS']['DVI']
    bld.add_action('.ltx', LaTeXAuxAction)
    bld.add_action('.latex', LaTeXAuxAction)
    bld.add_emitter('.ltx', SCons.Tool.tex.tex_eps_emitter)
    bld.add_emitter('.latex', SCons.Tool.tex.tex_eps_emitter)

    SCons.Tool.tex.generate_common(env)

def exists(env):
    SCons.Tool.tex.generate_darwin(env)
    return env.Detect('latex')

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/ldc.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


"""SCons.Tool.ldc

Tool-specific initialization for the LDC compiler.
(https://github.com/ldc-developers/ldc)

Developed by Russel Winder (russel@winder.org.uk)
2012-05-09 onwards

Compiler variables:
    DC - The name of the D compiler to use.  Defaults to ldc2.
    DPATH - List of paths to search for import modules.
    DVERSIONS - List of version tags to enable when compiling.
    DDEBUG - List of debug tags to enable when compiling.

Linker related variables:
    LIBS - List of library files to link in.
    DLINK - Name of the linker to use.  Defaults to ldc2.
    DLINKFLAGS - List of linker flags.

Lib tool variables:
    DLIB - Name of the lib tool to use.  Defaults to lib.
    DLIBFLAGS - List of flags to pass to the lib tool.
    LIBS - Same as for the linker. (libraries to pull into the .lib)
"""



import SCons.Action
import SCons.Builder
import SCons.Defaults
import SCons.Scanner.D
import SCons.Tool

import SCons.Tool.DCommon as DCommon


def generate(env):
    static_obj, shared_obj = SCons.Tool.createObjBuilders(env)

    static_obj.add_action('.d', SCons.Defaults.DAction)
    shared_obj.add_action('.d', SCons.Defaults.ShDAction)
    static_obj.add_emitter('.d', SCons.Defaults.StaticObjectEmitter)
    shared_obj.add_emitter('.d', SCons.Defaults.SharedObjectEmitter)

    env['DC'] = env.Detect('ldc2') or 'ldc2'
    env['DCOM'] = '$DC $_DINCFLAGS $_DVERFLAGS $_DDEBUGFLAGS $_DFLAGS -c -of=$TARGET $SOURCES'
    env['_DINCFLAGS'] = '${_concat(DINCPREFIX, DPATH, DINCSUFFIX, __env__, RDirs, TARGET, SOURCE)}'
    env['_DVERFLAGS'] = '${_concat(DVERPREFIX, DVERSIONS, DVERSUFFIX, __env__)}'
    env['_DDEBUGFLAGS'] = '${_concat(DDEBUGPREFIX, DDEBUG, DDEBUGSUFFIX, __env__)}'
    env['_DFLAGS'] = '${_concat(DFLAGPREFIX, DFLAGS, DFLAGSUFFIX, __env__)}'

    env['SHDC'] = '$DC'
    env['SHDCOM'] = '$DC $_DINCFLAGS $_DVERFLAGS $_DDEBUGFLAGS $_DFLAGS -c -relocation-model=pic -of=$TARGET $SOURCES'

    env['DPATH'] = ['#/']
    env['DFLAGS'] = []
    env['DVERSIONS'] = []
    env['DDEBUG'] = []

    if env['DC']:
        DCommon.addDPATHToEnv(env, env['DC'])

    env['DINCPREFIX'] = '-I='
    env['DINCSUFFIX'] = ''
    env['DVERPREFIX'] = '-version='
    env['DVERSUFFIX'] = ''
    env['DDEBUGPREFIX'] = '-debug='
    env['DDEBUGSUFFIX'] = ''
    env['DFLAGPREFIX'] = '-'
    env['DFLAGSUFFIX'] = ''
    env['DFILESUFFIX'] = '.d'

    env['DLINK'] = '$DC'
    env['DLINKFLAGS'] = SCons.Util.CLVar('')
    env['DLINKCOM'] = '$DLINK -of=$TARGET $DLINKFLAGS $__DRPATH $SOURCES $_DLIBDIRFLAGS $_DLIBFLAGS'

    env['SHDLINK'] = '$DC'
    env['SHDLINKFLAGS'] = SCons.Util.CLVar('$DLINKFLAGS -shared -defaultlib=phobos2-ldc')

    env['SHDLINKCOM'] = '$DLINK -of=$TARGET $SHDLINKFLAGS $__SHDLIBVERSIONFLAGS $__DRPATH $SOURCES $_DLIBDIRFLAGS $_DLIBFLAGS -L-ldruntime-ldc'

    env['DLIBLINKPREFIX'] = '' if env['PLATFORM'] == 'win32' else '-L-l'
    env['DLIBLINKSUFFIX'] = '.lib' if env['PLATFORM'] == 'win32' else ''
    # env['_DLIBFLAGS'] = '${_concat(DLIBLINKPREFIX, LIBS, DLIBLINKSUFFIX, __env__, RDirs, TARGET, SOURCE)}'
    env['_DLIBFLAGS'] = '${_stripixes(DLIBLINKPREFIX, LIBS, DLIBLINKSUFFIX, LIBPREFIXES, LIBSUFFIXES,  __env__)}'

    env['DLIBDIRPREFIX'] = '-L-L'
    env['DLIBDIRSUFFIX'] = ''
    env['_DLIBDIRFLAGS'] = '${_concat(DLIBDIRPREFIX, LIBPATH, DLIBDIRSUFFIX, __env__, RDirs, TARGET, SOURCE)}'

    env['DLIB'] = 'lib' if env['PLATFORM'] == 'win32' else 'ar cr'
    env['DLIBCOM'] = '$DLIB $_DLIBFLAGS {0}$TARGET $SOURCES $_DLIBFLAGS'.format('-c ' if env['PLATFORM'] == 'win32' else '')

    # env['_DLIBFLAGS'] = '${_concat(DLIBFLAGPREFIX, DLIBFLAGS, DLIBFLAGSUFFIX, __env__)}'

    env['DLIBFLAGPREFIX'] = '-'
    env['DLIBFLAGSUFFIX'] = ''

    # __RPATH is set to $_RPATH in the platform specification if that
    # platform supports it.
    env['DRPATHPREFIX'] = '-L-Wl,-rpath,' if env['PLATFORM'] == 'darwin' else '-L-rpath='
    env['DRPATHSUFFIX'] = ''
    env['_DRPATH'] = '${_concat(DRPATHPREFIX, RPATH, DRPATHSUFFIX, __env__)}'

    # Support for versioned libraries
    env['_SHDLIBVERSIONFLAGS'] = '$SHDLIBVERSIONFLAGS -L-soname=$_SHLIBSONAME'


    env['BUILDERS']['ProgramAllAtOnce'] = SCons.Builder.Builder(
        action='$DC $_DINCFLAGS $_DVERFLAGS $_DDEBUGFLAGS $_DFLAGS -of=$TARGET $DLINKFLAGS $__DRPATH $SOURCES $_DLIBDIRFLAGS $_DLIBFLAGS',
        emitter=DCommon.allAtOnceEmitter,
    )


def exists(env):
    return env.Detect('ldc2')


# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/lex.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""Tool-specific initialization for lex.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""

import os.path
import sys

import SCons.Action
import SCons.Tool
import SCons.Util
import SCons.Warnings
from SCons.Platform.mingw import MINGW_DEFAULT_PATHS
from SCons.Platform.cygwin import CYGWIN_DEFAULT_PATHS
from SCons.Platform.win32 import CHOCO_DEFAULT_PATH

LexAction = SCons.Action.Action("$LEXCOM", "$LEXCOMSTR")

if sys.platform == 'win32':
    BINS = ['flex', 'lex', 'win_flex']
else:
    BINS = ["flex", "lex"]

def lexEmitter(target, source, env):
    sourceBase, sourceExt = os.path.splitext(SCons.Util.to_String(source[0]))

    if sourceExt == ".lm":           # If using Objective-C
        target = [sourceBase + ".m"] # the extension is ".m".

    # This emitter essentially tries to add to the target all extra
    # files generated by flex.

    # Different options that are used to trigger the creation of extra files.
    fileGenOptions = ["--header-file=", "--tables-file="]

    lexflags = env.subst("$LEXFLAGS", target=target, source=source)
    for option in SCons.Util.CLVar(lexflags):
        for fileGenOption in fileGenOptions:
            l = len(fileGenOption)
            if option[:l] == fileGenOption:
                # A file generating option is present, so add the
                # file name to the target list.
                fileName = option[l:].strip()
                target.append(fileName)
    return (target, source)

def get_lex_path(env, append_paths=False):
    """
    Find the path to the lex tool, searching several possible names

    Only called in the Windows case, so the default_path
    can be Windows-specific

    :param env: current construction environment
    :param append_paths: if set, add the path to the tool to PATH
    :return: path to lex tool, if found
    """
    for prog in BINS:
        bin_path = SCons.Tool.find_program_path(
            env,
            prog,
            default_paths=CHOCO_DEFAULT_PATH + MINGW_DEFAULT_PATHS + CYGWIN_DEFAULT_PATHS )
        if bin_path:
            if append_paths:
                env.AppendENVPath('PATH', os.path.dirname(bin_path))
            return bin_path

    SCons.Warnings.warn(
        SCons.Warnings.SConsWarning,
        'lex tool requested, but lex or flex binary not found in ENV PATH'
    )


def generate(env):
    """Add Builders and construction variables for lex to an Environment."""
    c_file, cxx_file = SCons.Tool.createCFileBuilders(env)

    # C
    c_file.add_action(".l", LexAction)
    c_file.add_emitter(".l", lexEmitter)

    c_file.add_action(".lex", LexAction)
    c_file.add_emitter(".lex", lexEmitter)

    # Objective-C
    cxx_file.add_action(".lm", LexAction)
    cxx_file.add_emitter(".lm", lexEmitter)

    # C++
    cxx_file.add_action(".ll", LexAction)
    cxx_file.add_emitter(".ll", lexEmitter)

    env["LEXFLAGS"] = SCons.Util.CLVar("")

    if sys.platform == 'win32':
        # ignore the return - we do not need the full path here
        _ = get_lex_path(env, append_paths=True)
        env["LEX"] = env.Detect(BINS)
        if not env.get("LEXUNISTD"):
            env["LEXUNISTD"] = SCons.Util.CLVar("")
        env["LEXCOM"] = "$LEX $LEXUNISTD $LEXFLAGS -t $SOURCES > $TARGET"
    else:
        env["LEX"] = env.Detect(BINS)
        env["LEXCOM"] = "$LEX $LEXFLAGS -t $SOURCES > $TARGET"

def exists(env):
    if sys.platform == 'win32':
        return get_lex_path(env)
    else:
        return env.Detect(BINS)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/link.py

#
# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""
Tool-specific initialization for the generic POSIX linker.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

import SCons.Tool
import SCons.Util
import SCons.Warnings
from SCons.Tool import createProgBuilder
from SCons.Tool.linkCommon import smart_link
from SCons.Tool.linkCommon.LoadableModule import setup_loadable_module_logic
from SCons.Tool.linkCommon.SharedLibrary import setup_shared_lib_logic


def generate(env):
    """Add Builders and construction variables for gnulink to an Environment."""
    createProgBuilder(env)

    setup_shared_lib_logic(env)
    setup_loadable_module_logic(env)

    env['SMARTLINK'] = smart_link
    env['LINK'] = "$SMARTLINK"
    env['LINKFLAGS'] = SCons.Util.CLVar('')

    # __RPATH is only set to something ($_RPATH typically) on platforms that support it.
    env['LINKCOM'] = '$LINK -o $TARGET $LINKFLAGS $__RPATH $SOURCES $_LIBDIRFLAGS $_LIBFLAGS'
    env['LIBDIRPREFIX'] = '-L'
    env['LIBDIRSUFFIX'] = ''
    env['_LIBFLAGS'] = '${_stripixes(LIBLINKPREFIX, LIBS, LIBLINKSUFFIX, LIBPREFIXES, LIBSUFFIXES, __env__)}'
    env['LIBLINKPREFIX'] = '-l'
    env['LIBLINKSUFFIX'] = ''


def exists(env):
    # This module isn't really a Tool on its own, it's common logic for
    # other linkers.
    return None

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/linkloc.py

"""SCons.Tool.linkloc

Tool specification for the LinkLoc linker for the Phar Lap ETS embedded
operating system.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import re

import SCons.Action
import SCons.Defaults
import SCons.Errors
import SCons.Tool
import SCons.Util

from SCons.Tool.MSCommon import msvs_exists, merge_default_version
from SCons.Tool.PharLapCommon import addPharLapPaths

_re_linker_command = re.compile(r'(\s)@\s*([^\s]+)')

def repl_linker_command(m):
    # Replaces any linker command file directives (e.g. "@foo.lnk") with
    # the actual contents of the file.
    try:
        with open(m.group(2), "r") as f:
            return m.group(1) + f.read()
    except IOError:
        # the linker should return an error if it can't
        # find the linker command file so we will remain quiet.
        # However, we will replace the @ with a # so we will not continue
        # to find it with recursive substitution
        return m.group(1) + '#' + m.group(2)

class LinklocGenerator:
    def __init__(self, cmdline):
        self.cmdline = cmdline

    def __call__(self, env, target, source, for_signature):
        if for_signature:
            # Expand the contents of any linker command files recursively
            subs = 1
            strsub = env.subst(self.cmdline, target=target, source=source)
            while subs:
                strsub, subs = _re_linker_command.subn(repl_linker_command, strsub)
            return strsub
        else:
            return "${TEMPFILE('" + self.cmdline + "')}"

def generate(env):
    """Add Builders and construction variables for ar to an Environment."""
    SCons.Tool.createSharedLibBuilder(env)
    SCons.Tool.createProgBuilder(env)

    env['SUBST_CMD_FILE'] = LinklocGenerator
    env['SHLINK']      = '$LINK'
    env['SHLINKFLAGS'] = SCons.Util.CLVar('$LINKFLAGS')
    env['SHLINKCOM']   = '${SUBST_CMD_FILE("$SHLINK $SHLINKFLAGS $_LIBDIRFLAGS $_LIBFLAGS -dll $TARGET $SOURCES")}'
    env['SHLIBEMITTER']= None
    env['LDMODULEEMITTER']= None
    env['LINK']        = "linkloc"
    env['LINKFLAGS']   = SCons.Util.CLVar('')
    env['LINKCOM']     = '${SUBST_CMD_FILE("$LINK $LINKFLAGS $_LIBDIRFLAGS $_LIBFLAGS -exe $TARGET $SOURCES")}'
    env['LIBDIRPREFIX']='-libpath '
    env['LIBDIRSUFFIX']=''
    env['LIBLINKPREFIX']='-lib '
    env['LIBLINKSUFFIX']='$LIBSUFFIX'

    # Set-up ms tools paths for default version
    merge_default_version(env)

    addPharLapPaths(env)

def exists(env):
    if msvs_exists(env):
        return env.Detect('linkloc')
    else:
        return 0

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/m4.py

"""SCons.Tool.m4

Tool-specific initialization for m4.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import SCons.Action
import SCons.Builder
import SCons.Util

def generate(env):
    """Add Builders and construction variables for m4 to an Environment."""
    M4Action = SCons.Action.Action('$M4COM', '$M4COMSTR')
    bld = SCons.Builder.Builder(action = M4Action, src_suffix = '.m4')

    env['BUILDERS']['M4'] = bld

    # .m4 files might include other files, and it would be pretty hard
    # to write a scanner for it, so let's just cd to the dir of the m4
    # file and run from there.
    # The src_suffix setup is like so: file.c.m4 -> file.c,
    # file.cpp.m4 -> file.cpp etc.
    env['M4']      = 'm4'
    env['M4FLAGS'] = SCons.Util.CLVar('-E')
    env['M4COM']   = 'cd ${SOURCE.rsrcdir} && $M4 $M4FLAGS < ${SOURCE.file} > ${TARGET.abspath}'

def exists(env):
    return env.Detect('m4')

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/masm.py

"""SCons.Tool.masm

Tool-specific initialization for the Microsoft Assembler.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import SCons.Defaults
import SCons.Tool
import SCons.Util

ASSuffixes = ['.s', '.asm', '.ASM']
ASPPSuffixes = ['.spp', '.SPP', '.sx']
if SCons.Util.case_sensitive_suffixes('.s', '.S'):
    ASPPSuffixes.extend(['.S'])
else:
    ASSuffixes.extend(['.S'])

def generate(env):
    """Add Builders and construction variables for masm to an Environment."""
    static_obj, shared_obj = SCons.Tool.createObjBuilders(env)

    for suffix in ASSuffixes:
        static_obj.add_action(suffix, SCons.Defaults.ASAction)
        shared_obj.add_action(suffix, SCons.Defaults.ASAction)
        static_obj.add_emitter(suffix, SCons.Defaults.StaticObjectEmitter)
        shared_obj.add_emitter(suffix, SCons.Defaults.SharedObjectEmitter)

    for suffix in ASPPSuffixes:
        static_obj.add_action(suffix, SCons.Defaults.ASPPAction)
        shared_obj.add_action(suffix, SCons.Defaults.ASPPAction)
        static_obj.add_emitter(suffix, SCons.Defaults.StaticObjectEmitter)
        shared_obj.add_emitter(suffix, SCons.Defaults.SharedObjectEmitter)

    env['AS']        = 'ml'
    env['ASFLAGS']   = SCons.Util.CLVar('/nologo')
    env['ASPPFLAGS'] = '$ASFLAGS'
    env['ASCOM']     = '$AS $ASFLAGS /c /Fo$TARGET $SOURCES'
    env['ASPPCOM']   = '$CC $ASPPFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS /c /Fo$TARGET $SOURCES'
    env['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 1

def exists(env):
    return env.Detect('ml')

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/midl.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""SCons.Tool.midl

Tool-specific initialization for midl (Microsoft IDL compiler).

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

import SCons.Action
import SCons.Builder
import SCons.Defaults
import SCons.Scanner.IDL
import SCons.Util

from .MSCommon import msvc_setup_env_tool

tool_name = 'midl'


def midl_emitter(target, source, env):
    """Produces a list of outputs from the MIDL compiler"""
    base, _ = SCons.Util.splitext(str(target[0]))
    tlb = target[0]
    incl = base + '.h'
    interface = base + '_i.c'
    targets = [tlb, incl, interface]

    midlcom = env['MIDLCOM']

    if midlcom.find('/proxy') != -1:
        proxy = base + '_p.c'
        targets.append(proxy)
    if midlcom.find('/dlldata') != -1:
        dlldata = base + '_data.c'
        targets.append(dlldata)

    return targets, source


idl_scanner = SCons.Scanner.IDL.IDLScan()

midl_action = SCons.Action.Action('$MIDLCOM', '$MIDLCOMSTR')

midl_builder = SCons.Builder.Builder(action=midl_action,
                                     src_suffix='.idl',
                                     suffix='.tlb',
                                     emitter=midl_emitter,
                                     source_scanner=idl_scanner)


def generate(env):
    """Add Builders and construction variables for midl to an Environment."""

    env['MIDL'] = 'MIDL.EXE'
    env['MIDLFLAGS'] = SCons.Util.CLVar('/nologo')
    env['MIDLCOM'] = '$MIDL $MIDLFLAGS /tlb ${TARGETS[0]} /h ${TARGETS[1]} /iid ${TARGETS[2]} /proxy ${TARGETS[3]} /dlldata ${TARGETS[4]} $SOURCE 2> NUL'
    env['BUILDERS']['TypeLibrary'] = midl_builder


def exists(env):
    return msvc_setup_env_tool(env, tool=tool_name)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/mingw.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""SCons.Tool.gcc

Tool-specific initialization for MinGW (http://www.mingw.org/)

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

import os
import os.path
import glob

import SCons.Action
import SCons.Builder
import SCons.Defaults
import SCons.Tool
import SCons.Util

mingw_base_paths = [
    r'c:\MinGW\bin',
    r'C:\cygwin64\bin',
    r'C:\msys64',
    r'C:\msys64\mingw64\bin',
    r'C:\cygwin\bin',
    r'C:\msys',
    r'C:\ProgramData\chocolatey\lib\mingw\tools\install\mingw64\bin'
]


def shlib_generator(target, source, env, for_signature):
    cmd = SCons.Util.CLVar(['$SHLINK', '$SHLINKFLAGS'])

    dll = env.FindIxes(target, 'SHLIBPREFIX', 'SHLIBSUFFIX')
    if dll: cmd.extend(['-o', dll])

    cmd.extend(['$SOURCES', '$_LIBDIRFLAGS', '$_LIBFLAGS'])

    implib = env.FindIxes(target, 'LIBPREFIX', 'LIBSUFFIX')
    if implib: cmd.append('-Wl,--out-implib,' + implib.get_string(for_signature))

    def_target = env.FindIxes(target, 'WINDOWSDEFPREFIX', 'WINDOWSDEFSUFFIX')
    insert_def = env.subst("$WINDOWS_INSERT_DEF")
    if insert_def not in ['', '0', 0] and def_target: \
            cmd.append('-Wl,--output-def,' + def_target.get_string(for_signature))

    return [cmd]


def shlib_emitter(target, source, env):
    dll = env.FindIxes(target, 'SHLIBPREFIX', 'SHLIBSUFFIX')
    no_import_lib = env.get('no_import_lib', 0)

    if not dll:
        raise SCons.Errors.UserError(
            "A shared library should have exactly one target with the suffix: %s Target(s) are:%s" % \
            (env.subst("$SHLIBSUFFIX"), ",".join([str(t) for t in target])))

    if not no_import_lib and \
            not env.FindIxes(target, 'LIBPREFIX', 'LIBSUFFIX'):
        # Create list of target libraries as strings
        targetStrings = env.ReplaceIxes(dll,
                                        'SHLIBPREFIX', 'SHLIBSUFFIX',
                                        'LIBPREFIX', 'LIBSUFFIX')

        # Now add file nodes to target list
        target.append(env.fs.File(targetStrings))

    # Append a def file target if there isn't already a def file target
    # or a def file source or the user has explicitly asked for the target
    # to be emitted.
    def_source = env.FindIxes(source, 'WINDOWSDEFPREFIX', 'WINDOWSDEFSUFFIX')
    def_target = env.FindIxes(target, 'WINDOWSDEFPREFIX', 'WINDOWSDEFSUFFIX')
    skip_def_insert = env.subst("$WINDOWS_INSERT_DEF") in ['', '0', 0]
    if not def_source and not def_target and not skip_def_insert:
        # Create list of target libraries and def files as strings
        targetStrings = env.ReplaceIxes(dll,
                                        'SHLIBPREFIX', 'SHLIBSUFFIX',
                                        'WINDOWSDEFPREFIX', 'WINDOWSDEFSUFFIX')

        # Now add file nodes to target list
        target.append(env.fs.File(targetStrings))

    return (target, source)


shlib_action = SCons.Action.Action(shlib_generator, '$SHLINKCOMSTR', generator=1)
ldmodule_action = SCons.Action.Action(shlib_generator, '$LDMODULECOMSTR', generator=1)

res_action = SCons.Action.Action('$RCCOM', '$RCCOMSTR')

res_builder = SCons.Builder.Builder(action=res_action, suffix='.o',
                                    source_scanner=SCons.Tool.SourceFileScanner)
SCons.Tool.SourceFileScanner.add_scanner('.rc', SCons.Defaults.CScan)

# This is what we search for to find mingw:
# key_program = 'mingw32-gcc'
key_program = 'mingw32-make'


def find_version_specific_mingw_paths():
    r"""
    One example of default mingw install paths is:
    C:\mingw-w64\x86_64-6.3.0-posix-seh-rt_v5-rev2\mingw64\bin

    Use glob'ing to find such and add to mingw_base_paths
    """
    new_paths = glob.glob(r"C:\mingw-w64\*\mingw64\bin")

    return new_paths


_mingw_all_paths = None

def get_mingw_paths():
    global _mingw_all_paths
    if _mingw_all_paths is None:
        _mingw_all_paths = mingw_base_paths + find_version_specific_mingw_paths()
    return _mingw_all_paths

def generate(env):
    # Check for reasoanble mingw default paths
    mingw_paths = get_mingw_paths()

    mingw = SCons.Tool.find_program_path(env, key_program, default_paths=mingw_paths)
    if mingw:
        mingw_bin_dir = os.path.dirname(mingw)

        # Adjust path if we found it in a chocolatey install
        if mingw_bin_dir == r'C:\ProgramData\chocolatey\bin':
            mingw_bin_dir = r'C:\ProgramData\chocolatey\lib\mingw\tools\install\mingw64\bin'

        env.AppendENVPath('PATH', mingw_bin_dir)

    # Most of mingw is the same as gcc and friends...
    gnu_tools = ['gcc', 'g++', 'gnulink', 'ar', 'gas', 'gfortran', 'm4']
    for tool in gnu_tools:
        SCons.Tool.Tool(tool)(env)

    # ... but a few things differ:
    env['CC'] = 'gcc'
    # make sure the msvc tool doesnt break us, it added a /flag
    if 'CCFLAGS' in env:
        # make sure its a CLVar to handle list or str cases
        if type(env['CCFLAGS']) is not SCons.Util.CLVar:
            env['CCFLAGS'] = SCons.Util.CLVar(env['CCFLAGS'])
        env['CCFLAGS'] = SCons.Util.CLVar(str(env['CCFLAGS']).replace('/nologo', ''))
    env['SHCCFLAGS'] = SCons.Util.CLVar('$CCFLAGS')
    env['CXX'] = 'g++'
    env['SHCXXFLAGS'] = SCons.Util.CLVar('$CXXFLAGS')
    env['SHLINKFLAGS'] = SCons.Util.CLVar('$LINKFLAGS -shared')
    env['SHLINKCOM'] = shlib_action
    env['SHLINKCOMSTR'] = shlib_generator
    env['LDMODULECOM'] = ldmodule_action
    env.Append(SHLIBEMITTER=[shlib_emitter])
    env.Append(LDMODULEEMITTER=[shlib_emitter])
    env['AS'] = 'as'

    env['WINDOWSDEFPREFIX'] = ''
    env['WINDOWSDEFSUFFIX'] = '.def'

    env['SHOBJSUFFIX'] = '.o'
    env['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 1
    env['RC'] = 'windres'
    env['RCFLAGS'] = SCons.Util.CLVar('')
    env['RCINCFLAGS'] = '${_concat(RCINCPREFIX, CPPPATH, RCINCSUFFIX, __env__, RDirs, TARGET, SOURCE, affect_signature=False)}'
    env['RCINCPREFIX'] = '--include-dir '
    env['RCINCSUFFIX'] = ''
    env['RCCOM'] = '$RC $_CPPDEFFLAGS $RCINCFLAGS ${RCINCPREFIX} ${SOURCE.dir} $RCFLAGS -i $SOURCE -o $TARGET'
    env['BUILDERS']['RES'] = res_builder

    # Some setting from the platform also have to be overridden:
    env['OBJSUFFIX'] = '.o'
    env['LIBPREFIX'] = 'lib'
    env['LIBSUFFIX'] = '.a'
    env['PROGSUFFIX'] = '.exe'

    # Handle new versioned shared library logic
    env['_SHLIBSUFFIX'] = '$SHLIBSUFFIX'
    env["SHLIBPREFIX"] = ""

    # Disable creating symlinks for versioned shared library.
    env['SHLIBNOVERSIONSYMLINKS'] = True
    env['LDMODULENOVERSIONSYMLINKS'] = True
    env['IMPLIBNOVERSIONSYMLINKS'] = True



def exists(env):
    mingw_paths = get_mingw_paths()
    mingw = SCons.Tool.find_program_path(env, key_program, default_paths=mingw_paths)
    if mingw:
        mingw_bin_dir = os.path.dirname(mingw)
        env.AppendENVPath('PATH', mingw_bin_dir)

    return mingw

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/msgfmt.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

""" msgfmt tool """

import sys
import os

import SCons.Action
import SCons.Tool
import SCons.Util
import SCons.Warnings
from SCons.Builder import BuilderBase
from SCons.Errors import StopError
from SCons.Platform.cygwin import CYGWIN_DEFAULT_PATHS
from SCons.Platform.mingw import MINGW_DEFAULT_PATHS
from SCons.Tool.GettextCommon import (
    _detect_msgfmt,
    _msgfmt_exists,
    # MsgfmtToolWarning,
    _read_linguas_from_files,
)

class _MOFileBuilder(BuilderBase):
    """The builder class for `MO` files.

    The reason for this builder to exists and its purpose is quite simillar
    as for `_POFileBuilder`. This time, we extend list of sources, not targets,
    and call `BuilderBase._execute()` only once (as we assume single-target
    here).
    """

    def _execute(self, env, target, source, *args, **kw):
        # Here we add support for 'LINGUAS_FILE' keyword. Emitter is not suitable
        # in this case, as it is called too late (after multiple sources
        # are handled single_source builder.

        linguas_files = None
        if 'LINGUAS_FILE' in env and env['LINGUAS_FILE'] is not None:
            linguas_files = env['LINGUAS_FILE']
            # This should prevent from endless recursion.
            env['LINGUAS_FILE'] = None
            # We read only languages. Suffixes shall be added automatically.
            linguas = _read_linguas_from_files(env, linguas_files)
            if SCons.Util.is_List(source):
                source.extend(linguas)
            elif source is not None:
                source = [source] + linguas
            else:
                source = linguas
        result = BuilderBase._execute(self, env, target, source, *args, **kw)
        if linguas_files is not None:
            env['LINGUAS_FILE'] = linguas_files
        return result


def _create_mo_file_builder(env, **kw):
    """ Create builder object for `MOFiles` builder """

    # FIXME: What factory use for source? Ours or their?
    kw['action'] = SCons.Action.Action('$MSGFMTCOM', '$MSGFMTCOMSTR')
    kw['suffix'] = '$MOSUFFIX'
    kw['src_suffix'] = '$POSUFFIX'
    kw['src_builder'] = '_POUpdateBuilder'
    kw['single_source'] = True
    return _MOFileBuilder(**kw)


def generate(env, **kw):
    """ Generate `msgfmt` tool """

    if sys.platform == 'win32':
        msgfmt = SCons.Tool.find_program_path(
            env, 'msgfmt', default_paths=MINGW_DEFAULT_PATHS + CYGWIN_DEFAULT_PATHS
        )
        if msgfmt:
            msgfmt_bin_dir = os.path.dirname(msgfmt)
            env.AppendENVPath('PATH', msgfmt_bin_dir)
        else:
            SCons.Warnings.warn(
                # MsgfmtToolWarning,  # using this breaks test, so keep:
                SCons.Warnings.SConsWarning,
                'msgfmt tool requested, but binary not found in ENV PATH',
            )

    try:
        env['MSGFMT'] = _detect_msgfmt(env)
    except StopError:
        env['MSGFMT'] = 'msgfmt'
    env.SetDefault(
        MSGFMTFLAGS=[SCons.Util.CLVar('-c')],
        MSGFMTCOM='$MSGFMT $MSGFMTFLAGS -o $TARGET $SOURCE',
        MSGFMTCOMSTR='',
        MOSUFFIX=['.mo'],
        POSUFFIX=['.po'],
    )
    env.Append(BUILDERS={'MOFiles': _create_mo_file_builder(env)})


def exists(env):
    """ Check if the tool exists """

    try:
        return _msgfmt_exists(env)
    except StopError:
        return False

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/msginit.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""Tool specific initialization of msginit tool."""

import sys
import os

import SCons.Action
import SCons.Tool
import SCons.Util
import SCons.Warnings
from SCons.Environment import _null
from SCons.Errors import StopError
from SCons.Platform.cygwin import CYGWIN_DEFAULT_PATHS
from SCons.Platform.mingw import MINGW_DEFAULT_PATHS
from SCons.Tool.GettextCommon import (
    _detect_msginit,
    _init_po_files,
    _msginit_exists,
    # MsginitToolWarning,
    _POFileBuilder,
)


def _optional_no_translator_flag(env):
    """Return '--no-translator' flag if we run *msginit(1)*  in non-interactive
    mode."""
    if 'POAUTOINIT' in env:
        autoinit = env['POAUTOINIT']
    else:
        autoinit = False
    if autoinit:
        return [SCons.Util.CLVar('--no-translator')]
    else:
        return [SCons.Util.CLVar('')]


def _POInitBuilder(env, **kw):
    """ Create builder object for `POInit` builder. """

    action = SCons.Action.Action(_init_po_files, None)
    return _POFileBuilder(env, action=action, target_alias='$POCREATE_ALIAS')


def _POInitBuilderWrapper(env, target=None, source=_null, **kw):
    """Wrapper for _POFileBuilder. We use it to make user's life easier.

    This wrapper checks for `$POTDOMAIN` construction variable (or override in
    `**kw`) and treats it appropriatelly.
    """
    if source is _null:
        if 'POTDOMAIN' in kw:
            domain = kw['POTDOMAIN']
        elif 'POTDOMAIN' in env:
            domain = env['POTDOMAIN']
        else:
            domain = 'messages'
        source = [domain]  # NOTE: Suffix shall be appended automatically
    return env._POInitBuilder(target, source, **kw)

def generate(env, **kw):
    """ Generate the `msginit` tool """

    if sys.platform == 'win32':
        msginit = SCons.Tool.find_program_path(
            env, 'msginit', default_paths=MINGW_DEFAULT_PATHS + CYGWIN_DEFAULT_PATHS
        )
        if msginit:
            msginit_bin_dir = os.path.dirname(msginit)
            env.AppendENVPath('PATH', msginit_bin_dir)
        else:
            SCons.Warnings.warn(
                # MsginitToolWarning,  # using this breaks test, so keep:
                SCons.Warnings.SConsWarning,
                'msginit tool requested, but binary not found in ENV PATH',
            )

    try:
        env['MSGINIT'] = _detect_msginit(env)
    except StopError:
        env['MSGINIT'] = 'msginit'
    msginitcom = (
        '$MSGINIT ${_MSGNoTranslator(__env__)} -l ${_MSGINITLOCALE}'
        + ' $MSGINITFLAGS -i $SOURCE -o $TARGET'
    )
    # NOTE: We set POTSUFFIX here, in case the 'xgettext' is not loaded
    #       (sometimes we really don't need it)
    env.SetDefault(
        POSUFFIX=['.po'],
        POTSUFFIX=['.pot'],
        _MSGINITLOCALE='${TARGET.filebase}',
        _MSGNoTranslator=_optional_no_translator_flag,
        MSGINITCOM=msginitcom,
        MSGINITCOMSTR='',
        MSGINITFLAGS=[],
        POAUTOINIT=False,
        POCREATE_ALIAS='po-create',
    )
    env.Append(BUILDERS={'_POInitBuilder': _POInitBuilder(env)})
    env.AddMethod(_POInitBuilderWrapper, 'POInit')
    env.AlwaysBuild(env.Alias('$POCREATE_ALIAS'))


def exists(env):
    """ Check if the tool exists """

    try:
        return _msginit_exists(env)
    except StopError:
        return False

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/msgmerge.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""Tool specific initialization for `msgmerge` tool."""

import sys
import os

import SCons.Action
import SCons.Tool
import SCons.Warnings
from SCons.Errors import StopError
from SCons.Platform.cygwin import CYGWIN_DEFAULT_PATHS
from SCons.Platform.mingw import MINGW_DEFAULT_PATHS
from SCons.Tool.GettextCommon import (
    _detect_msgmerge,
    _init_po_files,
    _msgmerge_exists,
    # MsgmergeToolWarning,
    _POFileBuilder,
)

def _update_or_init_po_files(target, source, env):
    """ Action function for `POUpdate` builder """

    for tgt in target:
        if tgt.rexists():
            action = SCons.Action.Action('$MSGMERGECOM', '$MSGMERGECOMSTR')
        else:
            action = _init_po_files
        status = action([tgt], source, env)
        if status:
            return status
    return 0


def _POUpdateBuilder(env, **kw):
    """ Create an object of `POUpdate` builder """

    action = SCons.Action.Action(_update_or_init_po_files, None)
    return _POFileBuilder(env, action=action, target_alias='$POUPDATE_ALIAS')


from SCons.Environment import _null


def _POUpdateBuilderWrapper(env, target=None, source=_null, **kw):
    """ Wrapper for `POUpdate` builder - make user's life easier """
    if source is _null:
        if 'POTDOMAIN' in kw:
            domain = kw['POTDOMAIN']
        elif 'POTDOMAIN' in env and env['POTDOMAIN']:
            domain = env['POTDOMAIN']
        else:
            domain = 'messages'
        source = [domain]  # NOTE: Suffix shall be appended automatically
    return env._POUpdateBuilder(target, source, **kw)


def generate(env, **kw):
    """ Generate the `msgmerge` tool """

    if sys.platform == 'win32':
        msgmerge = SCons.Tool.find_program_path(
            env, 'msgmerge', default_paths=MINGW_DEFAULT_PATHS + CYGWIN_DEFAULT_PATHS
        )
        if msgmerge:
            msgmerge_bin_dir = os.path.dirname(msgmerge)
            env.AppendENVPath('PATH', msgmerge_bin_dir)
        else:
            SCons.Warnings.warn(
                # MsgmergeToolWarning,  # using this breaks test, so keep:
                SCons.Warnings.SConsWarning,
                'msgmerge tool requested, but binary not found in ENV PATH',
            )
    try:
        env['MSGMERGE'] = _detect_msgmerge(env)
    except StopError:
        env['MSGMERGE'] = 'msgmerge'
    env.SetDefault(
        POTSUFFIX=['.pot'],
        POSUFFIX=['.po'],
        MSGMERGECOM='$MSGMERGE  $MSGMERGEFLAGS --update $TARGET $SOURCE',
        MSGMERGECOMSTR='',
        MSGMERGEFLAGS=[],
        POUPDATE_ALIAS='po-update',
    )
    env.Append(BUILDERS={'_POUpdateBuilder': _POUpdateBuilder(env)})
    env.AddMethod(_POUpdateBuilderWrapper, 'POUpdate')
    env.AlwaysBuild(env.Alias('$POUPDATE_ALIAS'))


def exists(env):
    """ Check if the tool exists """

    try:
        return _msgmerge_exists(env)
    except StopError:
        return False

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/mslib.py

"""SCons.Tool.mslib

Tool-specific initialization for lib (MicroSoft library archiver).

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import os

import SCons.Defaults
import SCons.Tool
import SCons.Tool.msvs
import SCons.Tool.msvc
import SCons.Util

from .MSCommon import msvc_setup_env_tool, msvc_setup_env_once

tool_name = 'mslib'

def generate(env):
    """Add Builders and construction variables for lib to an Environment."""
    SCons.Tool.createStaticLibBuilder(env)

    # Set-up ms tools paths
    msvc_setup_env_once(env, tool=tool_name)

    env['AR']          = 'lib'
    env['ARFLAGS']     = SCons.Util.CLVar('/nologo')
    env['ARCOM']       = "${TEMPFILE('$AR $ARFLAGS /OUT:$TARGET $SOURCES','$ARCOMSTR')}"
    env['LIBPREFIX']   = ''
    env['LIBSUFFIX']   = '.lib'

    # Issue #3350
    # Change tempfile argument joining character from a space to a newline
    # mslink will fail if any single line is too long, but is fine with many lines
    # in a tempfile
    env['TEMPFILEARGJOIN'] = os.linesep


def exists(env):
    return msvc_setup_env_tool(env, tool=tool_name)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/mslink.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""SCons.Tool.mslink

Tool-specific initialization for the Microsoft linker.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

import os
import os.path

import SCons.Action
import SCons.Defaults
import SCons.Errors
import SCons.Platform.win32
import SCons.Tool
import SCons.Tool.msvc
import SCons.Tool.msvs
import SCons.Util

from .MSCommon import msvc_setup_env_once, msvc_setup_env_tool
from .MSCommon.common import get_pch_node

tool_name = 'mslink'

def pdbGenerator(env, target, source, for_signature):
    try:
        return ['/PDB:%s' % target[0].attributes.pdb, '/DEBUG']
    except (AttributeError, IndexError):
        return None

def _dllTargets(target, source, env, for_signature, paramtp):
    listCmd = []
    dll = env.FindIxes(target, '%sPREFIX' % paramtp, '%sSUFFIX' % paramtp)
    if dll: listCmd.append("/out:%s"%dll.get_string(for_signature))

    implib = env.FindIxes(target, 'LIBPREFIX', 'LIBSUFFIX')
    if implib: listCmd.append("/implib:%s"%implib.get_string(for_signature))

    return listCmd

def _dllSources(target, source, env, for_signature, paramtp):
    listCmd = []

    deffile = env.FindIxes(source, "WINDOWSDEFPREFIX", "WINDOWSDEFSUFFIX")
    for src in source:
        # Check explicitly for a non-None deffile so that the __cmp__
        # method of the base SCons.Util.Proxy class used for some Node
        # proxies doesn't try to use a non-existent __dict__ attribute.
        if deffile and src == deffile:
            # Treat this source as a .def file.
            listCmd.append("/def:%s" % src.get_string(for_signature))
        else:
            # Just treat it as a generic source file.
            listCmd.append(src)
    return listCmd

def windowsShlinkTargets(target, source, env, for_signature):
    return _dllTargets(target, source, env, for_signature, 'SHLIB')

def windowsShlinkSources(target, source, env, for_signature):
    return _dllSources(target, source, env, for_signature, 'SHLIB')

def _windowsLdmodTargets(target, source, env, for_signature):
    """Get targets for loadable modules."""
    return _dllTargets(target, source, env, for_signature, 'LDMODULE')

def _windowsLdmodSources(target, source, env, for_signature):
    """Get sources for loadable modules."""
    return _dllSources(target, source, env, for_signature, 'LDMODULE')

def _dllEmitter(target, source, env, paramtp):
    """Common implementation of dll emitter."""
    SCons.Tool.msvc.validate_vars(env)

    extratargets = []
    extrasources = []

    dll = env.FindIxes(target, '%sPREFIX' % paramtp, '%sSUFFIX' % paramtp)
    no_import_lib = env.get('no_import_lib', 0)

    if not dll:
        raise SCons.Errors.UserError('A shared library should have exactly one target with the suffix: %s' % env.subst('$%sSUFFIX' % paramtp))

    insert_def = env.subst("$WINDOWS_INSERT_DEF")
    if insert_def not in ['', '0', 0] and \
       not env.FindIxes(source, "WINDOWSDEFPREFIX", "WINDOWSDEFSUFFIX"):

        # append a def file to the list of sources
        extrasources.append(
            env.ReplaceIxes(dll,
                            '%sPREFIX' % paramtp, '%sSUFFIX' % paramtp,
                            "WINDOWSDEFPREFIX", "WINDOWSDEFSUFFIX"))

    version_num, suite = SCons.Tool.msvs.msvs_parse_version(env.get('MSVS_VERSION', '6.0'))
    if version_num >= 8.0 and \
            (env.get('WINDOWS_INSERT_MANIFEST', 0) or env.get('WINDOWS_EMBED_MANIFEST', 0)):
        # MSVC 8 and above automatically generate .manifest files that must be installed
        extratargets.append(
            env.ReplaceIxes(dll,
                            '%sPREFIX' % paramtp, '%sSUFFIX' % paramtp,
                            "WINDOWSSHLIBMANIFESTPREFIX", "WINDOWSSHLIBMANIFESTSUFFIX"))

    if 'PDB' in env and env['PDB']:
        pdb = env.arg2nodes('$PDB', target=target, source=source)[0]
        extratargets.append(pdb)
        target[0].attributes.pdb = pdb

    pch_node = get_pch_node(env, target, source)
    if version_num >= 11.0 and pch_node:
        # MSVC 11 and above need the PCH object file to be added to the link line,
        # otherwise you get link error LNK2011.
        pchobj = SCons.Util.splitext(str(pch_node))[0] + '.obj'
        # print "prog_emitter, version %s, appending pchobj %s"%(version_num, pchobj)
        if pchobj not in extrasources:
            extrasources.append(pchobj)

    if not no_import_lib and \
       not env.FindIxes(target, "LIBPREFIX", "LIBSUFFIX"):
        # Append an import library to the list of targets.
        extratargets.append(
            env.ReplaceIxes(dll,
                            '%sPREFIX' % paramtp, '%sSUFFIX' % paramtp,
                            "LIBPREFIX", "LIBSUFFIX"))
        # and .exp file is created if there are exports from a DLL
        extratargets.append(
            env.ReplaceIxes(dll,
                            '%sPREFIX' % paramtp, '%sSUFFIX' % paramtp,
                            "WINDOWSEXPPREFIX", "WINDOWSEXPSUFFIX"))

    return (target+extratargets, source+extrasources)

def windowsLibEmitter(target, source, env):
    return _dllEmitter(target, source, env, 'SHLIB')

def ldmodEmitter(target, source, env):
    """Emitter for loadable modules.

    Loadable modules are identical to shared libraries on Windows, but building
    them is subject to different parameters (LDMODULE*).
    """
    return _dllEmitter(target, source, env, 'LDMODULE')

def prog_emitter(target, source, env):
    SCons.Tool.msvc.validate_vars(env)

    extratargets = []
    extrasources = []

    exe = env.FindIxes(target, "PROGPREFIX", "PROGSUFFIX")
    if not exe:
        raise SCons.Errors.UserError("An executable should have exactly one target with the suffix: %s" % env.subst("$PROGSUFFIX"))

    version_num, suite = SCons.Tool.msvs.msvs_parse_version(env.get('MSVS_VERSION', '6.0'))
    if version_num >= 8.0 and \
            (env.get('WINDOWS_INSERT_MANIFEST', 0) or env.get('WINDOWS_EMBED_MANIFEST', 0)):
        # MSVC 8 and above automatically generate .manifest files that have to be installed
        extratargets.append(
            env.ReplaceIxes(exe,
                            "PROGPREFIX", "PROGSUFFIX",
                            "WINDOWSPROGMANIFESTPREFIX", "WINDOWSPROGMANIFESTSUFFIX"))

    if 'PDB' in env and env['PDB']:
        pdb = env.arg2nodes('$PDB', target=target, source=source)[0]
        extratargets.append(pdb)
        target[0].attributes.pdb = pdb

    pch_node = get_pch_node(env, target, source)
    if version_num >= 11.0 and pch_node:
        # MSVC 11 and above need the PCH object file to be added to the link line,
        # otherwise you get link error LNK2011.
        pchobj = SCons.Util.splitext(str(pch_node))[0] + '.obj'
        # print "prog_emitter, version %s, appending pchobj %s"%(version_num, pchobj)
        if pchobj not in extrasources:
            extrasources.append(pchobj)

    return (target+extratargets,source+extrasources)

def RegServerFunc(target, source, env):
    if 'register' in env and env['register']:
        ret = regServerAction([target[0]], [source[0]], env)
        if ret:
            raise SCons.Errors.UserError("Unable to register %s" % target[0])
        else:
            print("Registered %s sucessfully" % target[0])
        return ret
    return 0

# These are the actual actions run to embed the manifest.
# They are only called from the Check versions below.
embedManifestExeAction = SCons.Action.Action('$MTEXECOM')
embedManifestDllAction = SCons.Action.Action('$MTSHLIBCOM')

def embedManifestDllCheck(target, source, env):
    """Function run by embedManifestDllCheckAction to check for existence of manifest
    and other conditions, and embed the manifest by calling embedManifestDllAction if so."""
    if env.get('WINDOWS_EMBED_MANIFEST', 0):
        manifestSrc = target[0].get_abspath() + '.manifest'
        if os.path.exists(manifestSrc):
            ret = embedManifestDllAction([target[0]], None, env)
            if ret:
                raise SCons.Errors.UserError("Unable to embed manifest into %s" % (target[0]))
            return ret
        else:
            print('(embed: no %s.manifest found; not embedding.)'%str(target[0]))
    return 0

def embedManifestExeCheck(target, source, env):
    """Function run by embedManifestExeCheckAction to check for existence of manifest
    and other conditions, and embed the manifest by calling embedManifestExeAction if so."""
    if env.get('WINDOWS_EMBED_MANIFEST', 0):
        manifestSrc = target[0].get_abspath() + '.manifest'
        if os.path.exists(manifestSrc):
            ret = embedManifestExeAction([target[0]], None, env)
            if ret:
                raise SCons.Errors.UserError("Unable to embed manifest into %s" % (target[0]))
            return ret
        else:
            print('(embed: no %s.manifest found; not embedding.)'%str(target[0]))
    return 0

embedManifestDllCheckAction = SCons.Action.Action(embedManifestDllCheck, None)
embedManifestExeCheckAction = SCons.Action.Action(embedManifestExeCheck, None)

regServerAction = SCons.Action.Action("$REGSVRCOM", "$REGSVRCOMSTR")
regServerCheck = SCons.Action.Action(RegServerFunc, None)
shlibLinkAction = SCons.Action.Action('${TEMPFILE("$SHLINK $SHLINKFLAGS $_SHLINK_TARGETS $_LIBDIRFLAGS $_LIBFLAGS $_PDB $_SHLINK_SOURCES", "$SHLINKCOMSTR")}', '$SHLINKCOMSTR')
compositeShLinkAction = shlibLinkAction + regServerCheck + embedManifestDllCheckAction
ldmodLinkAction = SCons.Action.Action('${TEMPFILE("$LDMODULE $LDMODULEFLAGS $_LDMODULE_TARGETS $_LIBDIRFLAGS $_LIBFLAGS $_PDB $_LDMODULE_SOURCES", "$LDMODULECOMSTR")}', '$LDMODULECOMSTR')
compositeLdmodAction = ldmodLinkAction + regServerCheck + embedManifestDllCheckAction
exeLinkAction = SCons.Action.Action('${TEMPFILE("$LINK $LINKFLAGS /OUT:$TARGET.windows $_LIBDIRFLAGS $_LIBFLAGS $_PDB $SOURCES.windows", "$LINKCOMSTR")}', '$LINKCOMSTR')
compositeLinkAction = exeLinkAction + embedManifestExeCheckAction

def generate(env):
    """Add Builders and construction variables for ar to an Environment."""
    SCons.Tool.createSharedLibBuilder(env, shlib_suffix='$SHLIBSUFFIX')
    SCons.Tool.createProgBuilder(env)

    env['SHLINK'] = '$LINK'
    env['SHLINKFLAGS'] = SCons.Util.CLVar('$LINKFLAGS /dll')
    env['_SHLINK_TARGETS'] = windowsShlinkTargets
    env['_SHLINK_SOURCES'] = windowsShlinkSources
    env['SHLINKCOM'] = compositeShLinkAction
    env.Append(SHLIBEMITTER=[windowsLibEmitter])
    env.Append(LDMODULEEMITTER=[windowsLibEmitter])
    env['LINK'] = 'link'
    env['LINKFLAGS'] = SCons.Util.CLVar('/nologo')
    env['_PDB'] = pdbGenerator
    env['LINKCOM'] = compositeLinkAction
    env.Append(PROGEMITTER=[prog_emitter])
    env['LIBDIRPREFIX'] = '/LIBPATH:'
    env['LIBDIRSUFFIX'] = ''
    env['LIBLINKPREFIX'] = ''
    env['LIBLINKSUFFIX'] = '$LIBSUFFIX'

    env['WINDOWSDEFPREFIX'] = ''
    env['WINDOWSDEFSUFFIX'] = '.def'
    env['WINDOWSEXPPREFIX'] = ''
    env['WINDOWSEXPSUFFIX'] = '.exp'
    env['WINDOWS_INSERT_DEF'] = 0

    env['WINDOWSSHLIBMANIFESTPREFIX'] = ''
    env['WINDOWSSHLIBMANIFESTSUFFIX'] = '${SHLIBSUFFIX}.manifest'
    env['WINDOWSPROGMANIFESTPREFIX'] = ''
    env['WINDOWSPROGMANIFESTSUFFIX'] = '${PROGSUFFIX}.manifest'

    env['REGSVRACTION'] = regServerCheck
    env['REGSVR'] = os.path.join(
        SCons.Platform.win32.get_system_root(), 'System32', 'regsvr32'
    )
    env['REGSVRFLAGS'] = '/s '
    env['REGSVRCOM'] = '$REGSVR $REGSVRFLAGS ${TARGET.windows}'

    env['WINDOWS_EMBED_MANIFEST'] = 0
    env['MT'] = 'mt'
    # env['MTFLAGS'] = ['-hashupdate']
    env['MTFLAGS'] = SCons.Util.CLVar('/nologo')
    # Note: use - here to prevent build failure if no manifest produced.
    # This seems much simpler than a fancy system using a function action to see
    # if the manifest actually exists before trying to run mt with it.
    env['MTEXECOM'] = '-$MT $MTFLAGS -manifest ${TARGET}.manifest $_MANIFEST_SOURCES -outputresource:$TARGET;1'
    env['MTSHLIBCOM'] = '-$MT $MTFLAGS -manifest ${TARGET}.manifest $_MANIFEST_SOURCES -outputresource:$TARGET;2'
    # TODO Future work garyo 27-Feb-11
    env['_MANIFEST_SOURCES'] = None  # _windowsManifestSources

    # Set-up ms tools paths
    msvc_setup_env_once(env, tool=tool_name)

    # Loadable modules are on Windows the same as shared libraries, but they
    # are subject to different build parameters (LDMODULE* variables).
    # Therefore LDMODULE* variables correspond as much as possible to
    # SHLINK*/SHLIB* ones.
    SCons.Tool.createLoadableModuleBuilder(env, loadable_module_suffix='$LDMODULESUFFIX')
    env['LDMODULE'] = '$SHLINK'
    env['LDMODULEPREFIX'] = '$SHLIBPREFIX'
    env['LDMODULESUFFIX'] = '$SHLIBSUFFIX'
    env['LDMODULEFLAGS'] = '$SHLINKFLAGS'
    env['_LDMODULE_TARGETS'] = _windowsLdmodTargets
    env['_LDMODULE_SOURCES'] = _windowsLdmodSources
    env['LDMODULEEMITTER'] = [ldmodEmitter]
    env['LDMODULECOM'] = compositeLdmodAction

    # Issue #3350
    # Change tempfile argument joining character from a space to a newline
    # mslink will fail if any single line is too long, but is fine with many lines
    # in a tempfile
    env['TEMPFILEARGJOIN'] = os.linesep

def exists(env):
    return msvc_setup_env_tool(env, tool=tool_name)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/mssdk.py

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

"""SCons.Tool.mssdk

Tool-specific initialization for Microsoft SDKs, both Platform
SDKs and Windows SDKs.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""

from .MSCommon import mssdk_exists, \
                     mssdk_setup_env

def generate(env):
    """Add construction variables for an MS SDK to an Environment."""
    mssdk_setup_env(env)

def exists(env):
    return mssdk_exists()

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/msvc.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""SCons.Tool.msvc

Tool-specific initialization for Microsoft Visual C/C++.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

import os.path
import os

import SCons.Action
import SCons.Builder
import SCons.Errors
import SCons.Platform.win32
import SCons.Tool
import SCons.Tool.msvs
import SCons.Util
import SCons.Warnings
import SCons.Scanner.RC

from .MSCommon import msvc_setup_env_tool, msvc_setup_env_once, msvc_version_to_maj_min, msvc_find_vswhere
from .MSCommon.common import get_pch_node

tool_name = 'msvc'

CSuffixes = ['.c', '.C']
CXXSuffixes = ['.cc', '.cpp', '.cxx', '.c++', '.C++']

def validate_vars(env):
    """Validate the PCH and PCHSTOP construction variables."""
    if 'PCH' in env and env['PCH']:
        if 'PCHSTOP' not in env:
            raise SCons.Errors.UserError("The PCHSTOP construction must be defined if PCH is defined.")
        if not SCons.Util.is_String(env['PCHSTOP']):
            raise SCons.Errors.UserError("The PCHSTOP construction variable must be a string: %r"%env['PCHSTOP'])

def msvc_set_PCHPDBFLAGS(env):
    """
    Set appropriate PCHPDBFLAGS for the MSVC version being used.
    """
    if env.get('MSVC_VERSION',False):
        maj, min = msvc_version_to_maj_min(env['MSVC_VERSION'])
        if maj < 8:
            env['PCHPDBFLAGS'] = SCons.Util.CLVar(['${(PDB and "/Yd") or ""}'])
        else:
            env['PCHPDBFLAGS'] = ''
    else:
        # Default if we can't determine which version of MSVC we're using
        env['PCHPDBFLAGS'] = SCons.Util.CLVar(['${(PDB and "/Yd") or ""}'])


def pch_emitter(target, source, env):
    """Adds the object file target."""

    validate_vars(env)

    pch = None
    obj = None

    for t in target:
        if SCons.Util.splitext(str(t))[1] == '.pch':
            pch = t
        if SCons.Util.splitext(str(t))[1] == '.obj':
            obj = t

    if not obj:
        obj = SCons.Util.splitext(str(pch))[0]+'.obj'

    target = [pch, obj] # pch must be first, and obj second for the PCHCOM to work

    return (target, source)


def object_emitter(target, source, env, parent_emitter):
    """Sets up the PCH dependencies for an object file."""

    validate_vars(env)

    parent_emitter(target, source, env)

    # Add a dependency, but only if the target (e.g. 'Source1.obj')
    # doesn't correspond to the pre-compiled header ('Source1.pch').
    # If the basenames match, then this was most likely caused by
    # someone adding the source file to both the env.PCH() and the
    # env.Program() calls, and adding the explicit dependency would
    # cause a cycle on the .pch file itself.
    #
    # See issue #2505 for a discussion of what to do if it turns
    # out this assumption causes trouble in the wild:
    # https://github.com/SCons/scons/issues/2505
    pch=get_pch_node(env, target, source)
    if pch:
        if str(target[0]) != SCons.Util.splitext(str(pch))[0] + '.obj':
            env.Depends(target, pch)

    return (target, source)

def static_object_emitter(target, source, env):
    return object_emitter(target, source, env,
                          SCons.Defaults.StaticObjectEmitter)

def shared_object_emitter(target, source, env):
    return object_emitter(target, source, env,
                          SCons.Defaults.SharedObjectEmitter)


def gen_ccpchflags(env, target, source, for_signature):
    """
    Generator for CCPCHFLAGS
    if PCH is not defined or evaluates to a false value, then return empty string.
    """
    pch_node = get_pch_node(env, target, source)
    if not pch_node:
        return ''
        
    return SCons.Util.CLVar(["/Yu$PCHSTOP", "/Fp%s" % pch_node])

pch_action = SCons.Action.Action('$PCHCOM', '$PCHCOMSTR')
pch_builder = SCons.Builder.Builder(action=pch_action, suffix='.pch',
                                    emitter=pch_emitter,
                                    source_scanner=SCons.Tool.SourceFileScanner)


# Logic to build .rc files into .res files (resource files)
res_scanner = SCons.Scanner.RC.RCScan()
res_action  = SCons.Action.Action('$RCCOM', '$RCCOMSTR')
res_builder = SCons.Builder.Builder(action=res_action,
                                    src_suffix='.rc',
                                    suffix='.res',
                                    src_builder=[],
                                    source_scanner=res_scanner)

def msvc_batch_key(action, env, target, source):
    """
    Returns a key to identify unique batches of sources for compilation.

    If batching is enabled (via the $MSVC_BATCH setting), then all
    target+source pairs that use the same action, defined by the same
    environment, and have the same target and source directories, will
    be batched.

    Returning None specifies that the specified target+source should not
    be batched with other compilations.
    """

    # Fixing MSVC_BATCH mode. Previous if did not work when MSVC_BATCH
    # was set to False. This new version should work better.
    # Note we need to do the env.subst so $MSVC_BATCH can be a reference to
    # another construction variable, which is why we test for False and 0
    # as strings.
    if 'MSVC_BATCH' not in env or env.subst('$MSVC_BATCH') in ('0', 'False', '', None):
        # We're not using batching; return no key.
        return None
    t = target[0]
    s = source[0]
    if os.path.splitext(t.name)[0] != os.path.splitext(s.name)[0]:
        # The base names are different, so this *must* be compiled
        # separately; return no key.
        return None
    return (id(action), id(env), t.dir, s.dir)

def msvc_output_flag(target, source, env, for_signature):
    """
    Returns the correct /Fo flag for batching.

    If batching is disabled or there's only one source file, then we
    return an /Fo string that specifies the target explicitly.  Otherwise,
    we return an /Fo string that just specifies the first target's
    directory (where the Visual C/C++ compiler will put the .obj files).
    """

    # Fixing MSVC_BATCH mode. Previous if did not work when MSVC_BATCH
    # was set to False. This new version should work better. Removed
    # len(source)==1 as batch mode can compile only one file
    # (and it also fixed problem with compiling only one changed file
    # with batch mode enabled)
    if 'MSVC_BATCH' not in env or env.subst('$MSVC_BATCH') in ('0', 'False', '', None):
        return '/Fo$TARGET'
    else:
        # The Visual C/C++ compiler requires a \ at the end of the /Fo
        # option to indicate an output directory.  We use os.sep here so
        # that the test(s) for this can be run on non-Windows systems
        # without having a hard-coded backslash mess up command-line
        # argument parsing.
        # Adding double os.sep's as if the TARGET.dir has a space or otherwise
        # needs to be quoted they are needed per MSVC's odd behavior
        # See: https://github.com/SCons/scons/issues/3106
        return '/Fo${TARGET.dir}' + os.sep*2

CAction = SCons.Action.Action("$CCCOM", "$CCCOMSTR",
                              batch_key=msvc_batch_key,
                              targets='$CHANGED_TARGETS')
ShCAction = SCons.Action.Action("$SHCCCOM", "$SHCCCOMSTR",
                                batch_key=msvc_batch_key,
                                targets='$CHANGED_TARGETS')
CXXAction = SCons.Action.Action("$CXXCOM", "$CXXCOMSTR",
                                batch_key=msvc_batch_key,
                                targets='$CHANGED_TARGETS')
ShCXXAction = SCons.Action.Action("$SHCXXCOM", "$SHCXXCOMSTR",
                                  batch_key=msvc_batch_key,
                                  targets='$CHANGED_TARGETS')




def generate(env):
    """Add Builders and construction variables for MSVC++ to an Environment."""
    static_obj, shared_obj = SCons.Tool.createObjBuilders(env)

    # TODO(batch):  shouldn't reach in to cmdgen this way; necessary
    # for now to bypass the checks in Builder.DictCmdGenerator.__call__()
    # and allow .cc and .cpp to be compiled in the same command line.
    static_obj.cmdgen.source_ext_match = False
    shared_obj.cmdgen.source_ext_match = False

    for suffix in CSuffixes:
        static_obj.add_action(suffix, CAction)
        shared_obj.add_action(suffix, ShCAction)
        static_obj.add_emitter(suffix, static_object_emitter)
        shared_obj.add_emitter(suffix, shared_object_emitter)

    for suffix in CXXSuffixes:
        static_obj.add_action(suffix, CXXAction)
        shared_obj.add_action(suffix, ShCXXAction)
        static_obj.add_emitter(suffix, static_object_emitter)
        shared_obj.add_emitter(suffix, shared_object_emitter)

    env['CCPDBFLAGS'] = SCons.Util.CLVar(['${(PDB and "/Z7") or ""}'])
    env['CCPCHFLAGS'] = gen_ccpchflags
    env['_MSVC_OUTPUT_FLAG'] = msvc_output_flag
    env['_CCCOMCOM']  = '$CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS $CCPCHFLAGS $CCPDBFLAGS'
    env['CC']         = 'cl'
    env['CCFLAGS']    = SCons.Util.CLVar('/nologo')
    env['CFLAGS']     = SCons.Util.CLVar('')
    env['CCCOM']      = '${TEMPFILE("$CC $_MSVC_OUTPUT_FLAG /c $CHANGED_SOURCES $CFLAGS $CCFLAGS $_CCCOMCOM","$CCCOMSTR")}'
    env['SHCC']       = '$CC'
    env['SHCCFLAGS']  = SCons.Util.CLVar('$CCFLAGS')
    env['SHCFLAGS']   = SCons.Util.CLVar('$CFLAGS')
    env['SHCCCOM']    = '${TEMPFILE("$SHCC $_MSVC_OUTPUT_FLAG /c $CHANGED_SOURCES $SHCFLAGS $SHCCFLAGS $_CCCOMCOM","$SHCCCOMSTR")}'
    env['CXX']        = '$CC'
    env['CXXFLAGS']   = SCons.Util.CLVar('$( /TP $)')
    env['CXXCOM']     = '${TEMPFILE("$CXX $_MSVC_OUTPUT_FLAG /c $CHANGED_SOURCES $CXXFLAGS $CCFLAGS $_CCCOMCOM","$CXXCOMSTR")}'
    env['SHCXX']      = '$CXX'
    env['SHCXXFLAGS'] = SCons.Util.CLVar('$CXXFLAGS')
    env['SHCXXCOM']   = '${TEMPFILE("$SHCXX $_MSVC_OUTPUT_FLAG /c $CHANGED_SOURCES $SHCXXFLAGS $SHCCFLAGS $_CCCOMCOM","$SHCXXCOMSTR")}'
    env['CPPDEFPREFIX']  = '/D'
    env['CPPDEFSUFFIX']  = ''
    env['INCPREFIX']  = '/I'
    env['INCSUFFIX']  = ''
#    env.Append(OBJEMITTER = [static_object_emitter])
#    env.Append(SHOBJEMITTER = [shared_object_emitter])
    env['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 1

    env['RC'] = 'rc'
    env['RCFLAGS'] = SCons.Util.CLVar('/nologo')
    env['RCSUFFIXES']=['.rc','.rc2']
    env['RCCOM'] = '$RC $_CPPDEFFLAGS $_CPPINCFLAGS $RCFLAGS /fo$TARGET $SOURCES'
    env['BUILDERS']['RES'] = res_builder
    env['OBJPREFIX']      = ''
    env['OBJSUFFIX']      = '.obj'
    env['SHOBJPREFIX']    = '$OBJPREFIX'
    env['SHOBJSUFFIX']    = '$OBJSUFFIX'

    # MSVC probably wont support unistd.h so default
    # without it for lex generation
    env["LEXUNISTD"] = SCons.Util.CLVar("--nounistd")

    # Get user specified vswhere location or locate.
    env['VSWHERE'] = env.get('VSWHERE', msvc_find_vswhere()) 

    # Set-up ms tools paths
    msvc_setup_env_once(env, tool=tool_name)

    env['CFILESUFFIX'] = '.c'
    env['CXXFILESUFFIX'] = '.cc'

    msvc_set_PCHPDBFLAGS(env)

    # Issue #3350
    # Change tempfile argument joining character from a space to a newline
    # mslink will fail if any single line is too long, but is fine with many lines
    # in a tempfile
    env['TEMPFILEARGJOIN'] = os.linesep


    env['PCHCOM'] = '$CXX /Fo${TARGETS[1]} $CXXFLAGS $CCFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS /c $SOURCES /Yc$PCHSTOP /Fp${TARGETS[0]} $CCPDBFLAGS $PCHPDBFLAGS'
    env['BUILDERS']['PCH'] = pch_builder

    if 'ENV' not in env:
        env['ENV'] = {}
    if 'SystemRoot' not in env['ENV']:    # required for dlls in the winsxs folders
        env['ENV']['SystemRoot'] = SCons.Platform.win32.get_system_root()

    env['CCDEPFLAGS'] = '/showIncludes'
    env["NINJA_DEPFILE_PARSE_FORMAT"] = 'msvc'


def exists(env):
    return msvc_setup_env_tool(env, tool=tool_name)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/msvs.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

""" Tool-specific initialization for Microsoft Visual Studio project files.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""

import base64
import uuid
import ntpath
import os
import pickle
import re
import sys

import SCons.Builder
import SCons.Node.FS
import SCons.Platform.win32
import SCons.Script.SConscript
import SCons.PathList
import SCons.Util
import SCons.Warnings
from SCons.Defaults import processDefines
from SCons.compat import PICKLE_PROTOCOL
from .MSCommon import msvc_setup_env_tool, msvc_setup_env_once

tool_name = 'msvs'

##############################################################################
# Below here are the classes and functions for generation of
# DSP/DSW/SLN/VCPROJ files.
##############################################################################

def xmlify(s):
    s = s.replace("&", "&amp;") # do this first
    s = s.replace("'", "&apos;")
    s = s.replace('"', "&quot;")
    s = s.replace('<', "&lt;")
    s = s.replace('>', "&gt;")
    s = s.replace('\n', '&#x0A;')
    return s


def processIncludes(includes, env, target, source):
    """
    Process a CPPPATH list in includes, given the env, target and source.
    Returns a list of directory paths. These paths are absolute so we avoid
    putting pound-prefixed paths in a Visual Studio project file.
    """
    return [env.Dir(i).abspath for i in
            SCons.PathList.PathList(includes).subst_path(env, target, source)]

# Work-in-progress
# individual elements each get a globally unique identifier.
# however, there also are some "well known" guids that either
# represent something, or are used as a namespace to base
# generating guids that should be "in" the namespace
#NAMESPACE_PROJECT   = uuid.UUID("{D9BD5916-F055-4D77-8C69-9448E02BF433}")
#NAMESPACE_SLN_GROUP = uuid.UUID("{2D0C29E0-512F-47BE-9AC4-F4CAE74AE16E}")
#NAMESPACE_INTERNAL  = uuid.UUID("{BAA4019E-6D67-4EF1-B3CB-AE6CD82E4060}")

# Kinds of projects, as used in solution files
#PROJECT_KIND_C      = "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}"
#PROJECT_KIND_PCL    = "{786C830F-07A1-408B-BD7F-6EE04809D6DB}"
#NAMESPACE_PRJ_FOLDER = "{66A26720-8FB5-11D2-AA7E-00C04F688DDE}"
#NAMESPACE_SLN_FOLDER = "{2150E333-8FDC-42A3-9474-1A3956D46DE8}"
#NAMESPACE_TEST       = "{3AC096D0-A1C2-E12C-1390-A8335801FDAB}"

def processFlags(flags, env):
    """
    If /std:c++XX is in flags then we need to ensure /Zc:__cplusplus is in
    flags to tell intellisense to respect our specified standard
    """
    if any(f.startswith('/std:c++') for f in flags) and \
       not any(f == '/Zc:__cplusplus' for f in flags):
        flags.append('/Zc:__cplusplus')
    return [env.subst(f) for f in flags]


external_makefile_guid = '{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}'

def _generateGUID(slnfile, name, namespace=external_makefile_guid):
    """Generates a GUID for the sln file to use.

    The uuid5 function is used to combine an existing namespace uuid -
    the one VS uses for C projects (external_makedile_guid) -
    and a combination of the solution file name (slnfile) and the
    project name (name).  We just need uniqueness/repeatability.

    Returns (str) "displayable" GUID, already wrapped in braces
    """

    # Normalize the slnfile path to a Windows path (\ separators) so
    # the generated file has a consistent GUID even if we generate
    # it on a non-Windows platform.
    slnfile = ntpath.normpath(str(slnfile))
    solution = uuid.uuid5(uuid.UUID(namespace), "%s-%s" %(slnfile, name))
    return '{' + str(solution).upper() + '}'


version_re = re.compile(r'(\d+\.\d+)(.*)')


def msvs_parse_version(s):
    """
    Split a Visual Studio version, which may in fact be something like
    '7.0Exp', into is version number (returned as a float) and trailing
    "suite" portion.
    """
    num, suite = version_re.match(s).groups()
    return float(num), suite

# This is how we re-invoke SCons from inside MSVS Project files.
# The problem is that we might have been invoked as either scons.bat
# or scons.py.  If we were invoked directly as scons.py, then we could
# use sys.argv[0] to find the SCons "executable," but that doesn't work
# if we were invoked as scons.bat, which uses "python -c" to execute
# things and ends up with "-c" as sys.argv[0].  Consequently, we have
# the MSVS Project file invoke SCons the same way that scons.bat does,
# which works regardless of how we were invoked.
def getExecScriptMain(env, xml=None):
    scons_home = env.get('SCONS_HOME')
    if not scons_home and 'SCONS_LIB_DIR' in os.environ:
        scons_home = os.environ['SCONS_LIB_DIR']
    if scons_home:
        exec_script_main = "from os.path import join; import sys; sys.path = [ r'%s' ] + sys.path; import SCons.Script; SCons.Script.main()" % scons_home
    else:
        version = SCons.__version__
        exec_script_main = "from os.path import join; import sys; sys.path = [ join(sys.prefix, 'Lib', 'site-packages', 'scons-%(version)s'), join(sys.prefix, 'scons-%(version)s'), join(sys.prefix, 'Lib', 'site-packages', 'scons'), join(sys.prefix, 'scons') ] + sys.path; import SCons.Script; SCons.Script.main()" % locals()
    if xml:
        exec_script_main = xmlify(exec_script_main)
    return exec_script_main

# The string for the Python executable we tell the Project file to use
# is either sys.executable or, if an external PYTHON_ROOT environment
# variable exists, $(PYTHON)ROOT\\python.exe (generalized a little to
# pluck the actual executable name from sys.executable).
try:
    python_root = os.environ['PYTHON_ROOT']
except KeyError:
    python_executable = sys.executable
else:
    python_executable = os.path.join('$$(PYTHON_ROOT)',
                                     os.path.split(sys.executable)[1])

class Config:
    pass

def splitFully(path):
    dir, base = os.path.split(path)
    if dir and dir != '' and dir != path:
        return splitFully(dir)+[base]
    if base == '':
        return []
    return [base]

def makeHierarchy(sources):
    """Break a list of files into a hierarchy; for each value, if it is a string,
       then it is a file.  If it is a dictionary, it is a folder.  The string is
       the original path of the file."""

    hierarchy = {}
    for file in sources:
        path = splitFully(file)
        if len(path):
            dict = hierarchy
            for part in path[:-1]:
                if part not in dict:
                    dict[part] = {}
                dict = dict[part]
            dict[path[-1]] = file
        #else:
        #    print 'Warning: failed to decompose path for '+str(file)
    return hierarchy

class _UserGenerator:
    """
    Base class for .dsp.user file generator
    """
    # Default instance values.
    # Ok ... a bit defensive, but it does not seem reasonable to crash the
    # build for a workspace user file. :-)
    usrhead = None
    usrdebg = None
    usrconf = None
    createfile = False
    def __init__(self, dspfile, source, env):
        # DebugSettings should be a list of debug dictionary sorted in the same order
        # as the target list and variants
        if 'variant' not in env:
            raise SCons.Errors.InternalError("You must specify a 'variant' argument (i.e. 'Debug' or " +\
                  "'Release') to create an MSVSProject.")
        elif SCons.Util.is_String(env['variant']):
            variants = [env['variant']]
        elif SCons.Util.is_List(env['variant']):
            variants = env['variant']

        if 'DebugSettings' not in env or env['DebugSettings'] is None:
            dbg_settings = []
        elif SCons.Util.is_Dict(env['DebugSettings']):
            dbg_settings = [env['DebugSettings']]
        elif SCons.Util.is_List(env['DebugSettings']):
            if len(env['DebugSettings']) != len(variants):
                raise SCons.Errors.InternalError("Sizes of 'DebugSettings' and 'variant' lists must be the same.")
            dbg_settings = []
            for ds in env['DebugSettings']:
                if SCons.Util.is_Dict(ds):
                    dbg_settings.append(ds)
                else:
                    dbg_settings.append({})
        else:
            dbg_settings = []

        if len(dbg_settings) == 1:
            dbg_settings = dbg_settings * len(variants)

        self.createfile = self.usrhead and self.usrdebg and self.usrconf and \
                            dbg_settings and bool([ds for ds in dbg_settings if ds])

        if self.createfile:
            dbg_settings = dict(list(zip(variants, dbg_settings)))
            for var, src in dbg_settings.items():
                # Update only expected keys
                trg = {}
                for key in [k for k in self.usrdebg.keys() if k in src]:
                    trg[key] = str(src[key])
                self.configs[var].debug = trg

    def UserHeader(self):
        encoding = self.env.subst('$MSVSENCODING')
        versionstr = self.versionstr
        self.usrfile.write(self.usrhead % locals())

    def UserProject(self):
        pass

    def Build(self):
        if not self.createfile:
            return
        try:
            filename = self.dspabs +'.user'
            self.usrfile = open(filename, 'w')
        except IOError as detail:
            raise SCons.Errors.InternalError('Unable to open "' + filename + '" for writing:' + str(detail))
        else:
            self.UserHeader()
            self.UserProject()
            self.usrfile.close()

V9UserHeader = """\
<?xml version="1.0" encoding="%(encoding)s"?>
<VisualStudioUserFile
\tProjectType="Visual C++"
\tVersion="%(versionstr)s"
\tShowAllFiles="false"
\t>
\t<Configurations>
"""

V9UserConfiguration = """\
\t\t<Configuration
\t\t\tName="%(variant)s|%(platform)s"
\t\t\t>
\t\t\t<DebugSettings
%(debug_settings)s
\t\t\t/>
\t\t</Configuration>
"""

V9DebugSettings = {
'Command':'$(TargetPath)',
'WorkingDirectory': None,
'CommandArguments': None,
'Attach':'false',
'DebuggerType':'3',
'Remote':'1',
'RemoteMachine': None,
'RemoteCommand': None,
'HttpUrl': None,
'PDBPath': None,
'SQLDebugging': None,
'Environment': None,
'EnvironmentMerge':'true',
'DebuggerFlavor': None,
'MPIRunCommand': None,
'MPIRunArguments': None,
'MPIRunWorkingDirectory': None,
'ApplicationCommand': None,
'ApplicationArguments': None,
'ShimCommand': None,
'MPIAcceptMode': None,
'MPIAcceptFilter': None,
}

class _GenerateV7User(_UserGenerator):
    """Generates a Project file for MSVS .NET"""
    def __init__(self, dspfile, source, env):
        if self.version_num >= 9.0:
            self.usrhead = V9UserHeader
            self.usrconf = V9UserConfiguration
            self.usrdebg = V9DebugSettings
        super().__init__(dspfile, source, env)

    def UserProject(self):
        confkeys = sorted(self.configs.keys())
        for kind in confkeys:
            variant = self.configs[kind].variant
            platform = self.configs[kind].platform
            debug = self.configs[kind].debug
            if debug:
                debug_settings = '\n'.join(['\t\t\t\t%s="%s"' % (key, xmlify(value))
                                            for key, value in debug.items()
                                            if value is not None])
                self.usrfile.write(self.usrconf % locals())
        self.usrfile.write('\t</Configurations>\n</VisualStudioUserFile>')

V10UserHeader = """\
<?xml version="1.0" encoding="%(encoding)s"?>
<Project ToolsVersion="%(versionstr)s" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
"""

V10UserConfiguration = """\
\t<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='%(variant)s|%(platform)s'">
%(debug_settings)s
\t</PropertyGroup>
"""

V10DebugSettings = {
'LocalDebuggerCommand': None,
'LocalDebuggerCommandArguments': None,
'LocalDebuggerEnvironment': None,
'DebuggerFlavor': 'WindowsLocalDebugger',
'LocalDebuggerWorkingDirectory': None,
'LocalDebuggerAttach': None,
'LocalDebuggerDebuggerType': None,
'LocalDebuggerMergeEnvironment': None,
'LocalDebuggerSQLDebugging': None,
'RemoteDebuggerCommand': None,
'RemoteDebuggerCommandArguments': None,
'RemoteDebuggerWorkingDirectory': None,
'RemoteDebuggerServerName': None,
'RemoteDebuggerConnection': None,
'RemoteDebuggerDebuggerType': None,
'RemoteDebuggerAttach': None,
'RemoteDebuggerSQLDebugging': None,
'DeploymentDirectory': None,
'AdditionalFiles': None,
'RemoteDebuggerDeployDebugCppRuntime': None,
'WebBrowserDebuggerHttpUrl': None,
'WebBrowserDebuggerDebuggerType': None,
'WebServiceDebuggerHttpUrl': None,
'WebServiceDebuggerDebuggerType': None,
'WebServiceDebuggerSQLDebugging': None,
}

class _GenerateV10User(_UserGenerator):
    """Generates a Project'user file for MSVS 2010 or later"""

    def __init__(self, dspfile, source, env):
        version_num, suite = msvs_parse_version(env['MSVS_VERSION'])
        if version_num >= 14.3:
            # Visual Studio 2022 is considered to be version 17.
            self.versionstr = '17.0'
        elif version_num >= 14.2:
            # Visual Studio 2019 is considered to be version 16.
            self.versionstr = '16.0'
        elif version_num >= 14.1:
            # Visual Studio 2017 is considered to be version 15.
            self.versionstr = '15.0'
        elif version_num == 14.0:
            self.versionstr = '14.0'
        else:
            self.versionstr = '4.0'
        self.usrhead = V10UserHeader
        self.usrconf = V10UserConfiguration
        self.usrdebg = V10DebugSettings
        super().__init__(dspfile, source, env)

    def UserProject(self):
        confkeys = sorted(self.configs.keys())
        for kind in confkeys:
            variant = self.configs[kind].variant
            platform = self.configs[kind].platform
            debug = self.configs[kind].debug
            if debug:
                debug_settings = '\n'.join(['\t\t<%s>%s</%s>' % (key, xmlify(value), key)
                                            for key, value in debug.items()
                                            if value is not None])
                self.usrfile.write(self.usrconf % locals())
        self.usrfile.write('</Project>')

class _DSPGenerator:
    """ Base class for DSP generators """

    srcargs = [
        'srcs',
        'incs',
        'localincs',
        'resources',
        'misc']

    def __init__(self, dspfile, source, env):
        self.dspfile = str(dspfile)
        try:
            get_abspath = dspfile.get_abspath
        except AttributeError:
            self.dspabs = os.path.abspath(dspfile)
        else:
            self.dspabs = get_abspath()

        if 'variant' not in env:
            raise SCons.Errors.InternalError("You must specify a 'variant' argument (i.e. 'Debug' or " +\
                  "'Release') to create an MSVSProject.")
        elif SCons.Util.is_String(env['variant']):
            variants = [env['variant']]
        elif SCons.Util.is_List(env['variant']):
            variants = env['variant']

        if 'buildtarget' not in env or env['buildtarget'] is None:
            buildtarget = ['']
        elif SCons.Util.is_String(env['buildtarget']):
            buildtarget = [env['buildtarget']]
        elif SCons.Util.is_List(env['buildtarget']):
            if len(env['buildtarget']) != len(variants):
                raise SCons.Errors.InternalError("Sizes of 'buildtarget' and 'variant' lists must be the same.")
            buildtarget = []
            for bt in env['buildtarget']:
                if SCons.Util.is_String(bt):
                    buildtarget.append(bt)
                else:
                    buildtarget.append(bt.get_abspath())
        else:
            buildtarget = [env['buildtarget'].get_abspath()]
        if len(buildtarget) == 1:
            bt = buildtarget[0]
            buildtarget = []
            for _ in variants:
                buildtarget.append(bt)

        if 'outdir' not in env or env['outdir'] is None:
            outdir = ['']
        elif SCons.Util.is_String(env['outdir']):
            outdir = [env['outdir']]
        elif SCons.Util.is_List(env['outdir']):
            if len(env['outdir']) != len(variants):
                raise SCons.Errors.InternalError("Sizes of 'outdir' and 'variant' lists must be the same.")
            outdir = []
            for s in env['outdir']:
                if SCons.Util.is_String(s):
                    outdir.append(s)
                else:
                    outdir.append(s.get_abspath())
        else:
            outdir = [env['outdir'].get_abspath()]
        if len(outdir) == 1:
            s = outdir[0]
            outdir = []
            for v in variants:
                outdir.append(s)

        if 'runfile' not in env or env['runfile'] is None:
            runfile = buildtarget[-1:]
        elif SCons.Util.is_String(env['runfile']):
            runfile = [env['runfile']]
        elif SCons.Util.is_List(env['runfile']):
            if len(env['runfile']) != len(variants):
                raise SCons.Errors.InternalError("Sizes of 'runfile' and 'variant' lists must be the same.")
            runfile = []
            for s in env['runfile']:
                if SCons.Util.is_String(s):
                    runfile.append(s)
                else:
                    runfile.append(s.get_abspath())
        else:
            runfile = [env['runfile'].get_abspath()]
        if len(runfile) == 1:
            s = runfile[0]
            runfile = []
            for v in variants:
                runfile.append(s)

        self.sconscript = env['MSVSSCONSCRIPT']

        def GetKeyFromEnv(env, key, variants):
            """
            Retrieves a specific key from the environment. If the key is
            present, it is expected to either be a string or a list with length
            equal to the number of variants. The function returns a list of
            the desired value (e.g. cpp include paths) guaranteed to be of
            length equal to the length of the variants list.
            """
            if key not in env or env[key] is None:
                return [''] * len(variants)
            elif SCons.Util.is_String(env[key]):
                return [env[key]] * len(variants)
            elif SCons.Util.is_List(env[key]):
                if len(env[key]) != len(variants):
                    raise SCons.Errors.InternalError("Sizes of '%s' and 'variant' lists must be the same." % key)
                else:
                    return env[key]
            else:
                raise SCons.Errors.InternalError("Unsupported type for key '%s' in environment: %s" %
                                                 (key, type(env[key])))

        cmdargs = GetKeyFromEnv(env, 'cmdargs', variants)

        # The caller is allowed to put 'cppdefines' and/or 'cpppaths' in the
        # environment, which is useful if they want to provide per-variant
        # values for these. Otherwise, we fall back to using the global
        # 'CPPDEFINES' and 'CPPPATH' functions.
        if 'cppdefines' in env:
            cppdefines = GetKeyFromEnv(env, 'cppdefines', variants)
        else:
            cppdefines = [env.get('CPPDEFINES', [])] * len(variants)
        if 'cpppaths' in env:
            cpppaths = GetKeyFromEnv(env, 'cpppaths', variants)
        else:
            cpppaths = [env.get('CPPPATH', [])] * len(variants)
        if 'cppflags' in env:
            cppflags = GetKeyFromEnv(env, 'cppflags', variants)
        else:
            cppflags = [env.get('CCFLAGS', []) + env.get('CXXFLAGS', []) + env.get('CPPFLAGS', [])] * len(variants)

        self.env = env

        if 'name' in self.env:
            self.name = self.env['name']
        else:
            self.name = os.path.basename(SCons.Util.splitext(self.dspfile)[0])
        self.name = self.env.subst(self.name)

        sourcenames = [
            'Source Files',
            'Header Files',
            'Local Headers',
            'Resource Files',
            'Other Files']

        self.sources = {}
        for n in sourcenames:
            self.sources[n] = []

        self.configs = {}

        self.nokeep = 0
        if 'nokeep' in env and env['variant'] != 0:
            self.nokeep = 1

        if self.nokeep == 0 and os.path.exists(self.dspabs):
            self.Parse()

        for t in zip(sourcenames,self.srcargs):
            if t[1] in self.env:
                if SCons.Util.is_List(self.env[t[1]]):
                    for i in self.env[t[1]]:
                        if i not in self.sources[t[0]]:
                            self.sources[t[0]].append(i)
                else:
                    if not self.env[t[1]] in self.sources[t[0]]:
                        self.sources[t[0]].append(self.env[t[1]])

        for n in sourcenames:
            self.sources[n].sort(key=lambda a: a.lower())

        def AddConfig(self, variant, buildtarget, outdir, runfile, cmdargs, cppdefines, cpppaths, cppflags, dspfile=dspfile, env=env):
            config = Config()
            config.buildtarget = buildtarget
            config.outdir = outdir
            config.cmdargs = cmdargs
            config.cppdefines = cppdefines
            config.cppflags = cppflags
            config.runfile = runfile

            # Dir objects can't be pickled, so we need an absolute path here.
            config.cpppaths = processIncludes(cpppaths, env, None, None)

            match = re.match(r'(.*)\|(.*)', variant)
            if match:
                config.variant = match.group(1)
                config.platform = match.group(2)
            else:
                config.variant = variant
                config.platform = 'Win32'

            self.configs[variant] = config
            # DEBUG: leave enabled, test/MSVS/CPPPATH-dirs.py expects this
            print("Adding '" + self.name + ' - ' + config.variant + '|' + config.platform + "' to '" + str(dspfile) + "'")

        for i in range(len(variants)):
            AddConfig(self, variants[i], buildtarget[i], outdir[i], runfile[i], cmdargs[i], cppdefines[i], cpppaths[i], cppflags[i])

        seen = set()
        self.platforms = [p.platform for p in self.configs.values()
                          if not (p.platform in seen or seen.add(p.platform))]


    def Build(self):
        pass

V6DSPHeader = """\
# Microsoft Developer Studio Project File - Name="%(name)s" - Package Owner=<4>
# Microsoft Developer Studio Generated Build File, Format Version 6.00
# ** DO NOT EDIT **

# TARGTYPE "Win32 (x86) External Target" 0x0106

CFG=%(name)s - Win32 %(confkey)s
!MESSAGE This is not a valid makefile. To build this project using NMAKE,
!MESSAGE use the Export Makefile command and run
!MESSAGE
!MESSAGE NMAKE /f "%(name)s.mak".
!MESSAGE
!MESSAGE You can specify a configuration when running NMAKE
!MESSAGE by defining the macro CFG on the command line. For example:
!MESSAGE
!MESSAGE NMAKE /f "%(name)s.mak" CFG="%(name)s - Win32 %(confkey)s"
!MESSAGE
!MESSAGE Possible choices for configuration are:
!MESSAGE
"""

class _GenerateV6DSP(_DSPGenerator):
    """Generates a Project file for MSVS 6.0"""

    def PrintHeader(self):
        # pick a default config
        confkeys = sorted(self.configs.keys())

        name = self.name
        confkey = confkeys[0]

        self.file.write(V6DSPHeader % locals())

        for kind in confkeys:
            self.file.write('!MESSAGE "%s - Win32 %s" (based on "Win32 (x86) External Target")\n' % (name, kind))

        self.file.write('!MESSAGE\n\n')

    def PrintProject(self):
        name = self.name
        self.file.write('# Begin Project\n'
                        '# PROP AllowPerConfigDependencies 0\n'
                        '# PROP Scc_ProjName ""\n'
                        '# PROP Scc_LocalPath ""\n\n')

        first = 1
        confkeys = sorted(self.configs.keys())
        for kind in confkeys:
            outdir = self.configs[kind].outdir
            buildtarget = self.configs[kind].buildtarget
            if first == 1:
                self.file.write('!IF  "$(CFG)" == "%s - Win32 %s"\n\n' % (name, kind))
                first = 0
            else:
                self.file.write('\n!ELSEIF  "$(CFG)" == "%s - Win32 %s"\n\n' % (name, kind))

            env_has_buildtarget = 'MSVSBUILDTARGET' in self.env
            if not env_has_buildtarget:
                self.env['MSVSBUILDTARGET'] = buildtarget

            # have to write this twice, once with the BASE settings, and once without
            for base in ("BASE ",""):
                self.file.write('# PROP %sUse_MFC 0\n'
                                '# PROP %sUse_Debug_Libraries ' % (base, base))
                if 'debug' not in kind.lower():
                    self.file.write('0\n')
                else:
                    self.file.write('1\n')
                self.file.write('# PROP %sOutput_Dir "%s"\n'
                                '# PROP %sIntermediate_Dir "%s"\n' % (base,outdir,base,outdir))
                cmd = 'echo Starting SCons && ' + self.env.subst('$MSVSBUILDCOM', 1)
                self.file.write('# PROP %sCmd_Line "%s"\n'
                                '# PROP %sRebuild_Opt "-c && %s"\n'
                                '# PROP %sTarget_File "%s"\n'
                                '# PROP %sBsc_Name ""\n'
                                '# PROP %sTarget_Dir ""\n'\
                                %(base,cmd,base,cmd,base,buildtarget,base,base))

            if not env_has_buildtarget:
                del self.env['MSVSBUILDTARGET']

        self.file.write('\n!ENDIF\n\n'
                        '# Begin Target\n\n')
        for kind in confkeys:
            self.file.write('# Name "%s - Win32 %s"\n' % (name,kind))
        self.file.write('\n')
        first = 0
        for kind in confkeys:
            if first == 0:
                self.file.write('!IF  "$(CFG)" == "%s - Win32 %s"\n\n' % (name,kind))
                first = 1
            else:
                self.file.write('!ELSEIF  "$(CFG)" == "%s - Win32 %s"\n\n' % (name,kind))
        self.file.write('!ENDIF\n\n')
        self.PrintSourceFiles()
        self.file.write('# End Target\n'
                        '# End Project\n')

        if self.nokeep == 0:
            # now we pickle some data and add it to the file -- MSDEV will ignore it.
            pdata = pickle.dumps(self.configs,PICKLE_PROTOCOL)
            pdata = base64.b64encode(pdata).decode()
            self.file.write(pdata + '\n')
            pdata = pickle.dumps(self.sources,PICKLE_PROTOCOL)
            pdata = base64.b64encode(pdata).decode()
            self.file.write(pdata + '\n')

    def PrintSourceFiles(self):
        categories = {'Source Files': 'cpp|c|cxx|l|y|def|odl|idl|hpj|bat',
                      'Header Files': 'h|hpp|hxx|hm|inl',
                      'Local Headers': 'h|hpp|hxx|hm|inl',
                      'Resource Files': 'r|rc|ico|cur|bmp|dlg|rc2|rct|bin|cnt|rtf|gif|jpg|jpeg|jpe',
                      'Other Files': ''}

        for kind in sorted(categories.keys(), key=lambda a: a.lower()):
            if not self.sources[kind]:
                continue # skip empty groups

            self.file.write('# Begin Group "' + kind + '"\n\n')
            typelist = categories[kind].replace('|', ';')
            self.file.write('# PROP Default_Filter "' + typelist + '"\n')

            for file in self.sources[kind]:
                file = os.path.normpath(file)
                self.file.write('# Begin Source File\n\n'
                                'SOURCE="' + file + '"\n'
                                '# End Source File\n')
            self.file.write('# End Group\n')

        # add the SConscript file outside of the groups
        self.file.write('# Begin Source File\n\n'
                        'SOURCE="' + str(self.sconscript) + '"\n'
                        '# End Source File\n')

    def Parse(self):
        try:
            dspfile = open(self.dspabs,'r')
        except IOError:
            return # doesn't exist yet, so can't add anything to configs.

        line = dspfile.readline()
        # skip until marker
        while line:
            if "# End Project" in line:
                break
            line = dspfile.readline()

        # read to get configs
        line = dspfile.readline()
        datas = line
        while line and line != '\n':
            line = dspfile.readline()
            datas = datas + line

        # OK, we've found our little pickled cache of data.
        try:
            datas = base64.decodestring(datas)
            data = pickle.loads(datas)
        except KeyboardInterrupt:
            raise
        except Exception:
            return # unable to unpickle any data for some reason

        self.configs.update(data)

        # keep reading to get sources
        data = None
        line = dspfile.readline()
        datas = line
        while line and line != '\n':
            line = dspfile.readline()
            datas = datas + line
        dspfile.close()

        # OK, we've found our little pickled cache of data.
        # it has a "# " in front of it, so we strip that.
        try:
            datas = base64.decodestring(datas)
            data = pickle.loads(datas)
        except KeyboardInterrupt:
            raise
        except:
            return # unable to unpickle any data for some reason

        self.sources.update(data)

    def Build(self):
        try:
            self.file = open(self.dspabs,'w')
        except IOError as detail:
            raise SCons.Errors.InternalError('Unable to open "' + self.dspabs + '" for writing:' + str(detail))
        else:
            self.PrintHeader()
            self.PrintProject()
            self.file.close()

V7DSPHeader = """\
<?xml version="1.0" encoding="%(encoding)s"?>
<VisualStudioProject
\tProjectType="Visual C++"
\tVersion="%(versionstr)s"
\tName="%(name)s"
\tProjectGUID="%(project_guid)s"
%(scc_attrs)s
\tKeyword="MakeFileProj">
"""

V7DSPConfiguration = """\
\t\t<Configuration
\t\t\tName="%(variant)s|%(platform)s"
\t\t\tOutputDirectory="%(outdir)s"
\t\t\tIntermediateDirectory="%(outdir)s"
\t\t\tConfigurationType="0"
\t\t\tUseOfMFC="0"
\t\t\tATLMinimizesCRunTimeLibraryUsage="FALSE">
\t\t\t<Tool
\t\t\t\tName="VCNMakeTool"
\t\t\t\tBuildCommandLine="%(buildcmd)s"
\t\t\t\tReBuildCommandLine="%(rebuildcmd)s"
\t\t\t\tCleanCommandLine="%(cleancmd)s"
\t\t\t\tOutput="%(runfile)s"/>
\t\t</Configuration>
"""

V8DSPHeader = """\
<?xml version="1.0" encoding="%(encoding)s"?>
<VisualStudioProject
\tProjectType="Visual C++"
\tVersion="%(versionstr)s"
\tName="%(name)s"
\tProjectGUID="%(project_guid)s"
\tRootNamespace="%(name)s"
%(scc_attrs)s
\tKeyword="MakeFileProj">
"""

V8DSPConfiguration = """\
\t\t<Configuration
\t\t\tName="%(variant)s|%(platform)s"
\t\t\tConfigurationType="0"
\t\t\tUseOfMFC="0"
\t\t\tATLMinimizesCRunTimeLibraryUsage="false"
\t\t\t>
\t\t\t<Tool
\t\t\t\tName="VCNMakeTool"
\t\t\t\tBuildCommandLine="%(buildcmd)s"
\t\t\t\tReBuildCommandLine="%(rebuildcmd)s"
\t\t\t\tCleanCommandLine="%(cleancmd)s"
\t\t\t\tOutput="%(runfile)s"
\t\t\t\tPreprocessorDefinitions="%(preprocdefs)s"
\t\t\t\tIncludeSearchPath="%(includepath)s"
\t\t\t\tForcedIncludes=""
\t\t\t\tAssemblySearchPath=""
\t\t\t\tForcedUsingAssemblies=""
\t\t\t\tCompileAsManaged=""
\t\t\t/>
\t\t</Configuration>
"""
class _GenerateV7DSP(_DSPGenerator, _GenerateV7User):
    """Generates a Project file for MSVS .NET"""

    def __init__(self, dspfile, source, env):
        _DSPGenerator.__init__(self, dspfile, source, env)
        self.version = env['MSVS_VERSION']
        self.version_num, self.suite = msvs_parse_version(self.version)
        if self.version_num >= 9.0:
            self.versionstr = '9.00'
            self.dspheader = V8DSPHeader
            self.dspconfiguration = V8DSPConfiguration
        elif self.version_num >= 8.0:
            self.versionstr = '8.00'
            self.dspheader = V8DSPHeader
            self.dspconfiguration = V8DSPConfiguration
        else:
            if self.version_num >= 7.1:
                self.versionstr = '7.10'
            else:
                self.versionstr = '7.00'
            self.dspheader = V7DSPHeader
            self.dspconfiguration = V7DSPConfiguration
        self.file = None

        _GenerateV7User.__init__(self, dspfile, source, env)

    def PrintHeader(self):
        env = self.env
        versionstr = self.versionstr
        name = self.name
        encoding = self.env.subst('$MSVSENCODING')
        scc_provider = env.get('MSVS_SCC_PROVIDER', '')
        scc_project_name = env.get('MSVS_SCC_PROJECT_NAME', '')
        scc_aux_path = env.get('MSVS_SCC_AUX_PATH', '')
        # MSVS_SCC_LOCAL_PATH is kept  for backwards compatibility purpose and should
        # be deprecated as soon as possible.
        scc_local_path_legacy = env.get('MSVS_SCC_LOCAL_PATH', '')
        scc_connection_root = env.get('MSVS_SCC_CONNECTION_ROOT', os.curdir)
        scc_local_path = os.path.relpath(scc_connection_root, os.path.dirname(self.dspabs))
        project_guid = env.get('MSVS_PROJECT_GUID', '')
        if not project_guid:
            project_guid = _generateGUID(self.dspfile, '')
        if scc_provider != '':
            scc_attrs = '\tSccProjectName="%s"\n' % scc_project_name
            if scc_aux_path != '':
                scc_attrs += '\tSccAuxPath="%s"\n' % scc_aux_path
            scc_attrs += ('\tSccLocalPath="%s"\n'
                          '\tSccProvider="%s"' % (scc_local_path, scc_provider))
        elif scc_local_path_legacy != '':
            # This case is kept for backwards compatibility purpose and should
            # be deprecated as soon as possible.
            scc_attrs = ('\tSccProjectName="%s"\n'
                         '\tSccLocalPath="%s"' % (scc_project_name, scc_local_path_legacy))
        else:
            self.dspheader = self.dspheader.replace('%(scc_attrs)s\n', '')

        self.file.write(self.dspheader % locals())

        self.file.write('\t<Platforms>\n')
        for platform in self.platforms:
            self.file.write(
                        '\t\t<Platform\n'
                        '\t\t\tName="%s"/>\n' % platform)
        self.file.write('\t</Platforms>\n')

        if self.version_num >= 8.0:
            self.file.write('\t<ToolFiles>\n'
                            '\t</ToolFiles>\n')

    def PrintProject(self):
        self.file.write('\t<Configurations>\n')

        confkeys = sorted(self.configs.keys())
        for kind in confkeys:
            variant = self.configs[kind].variant
            platform = self.configs[kind].platform
            outdir = self.configs[kind].outdir
            buildtarget = self.configs[kind].buildtarget
            runfile     = self.configs[kind].runfile
            cmdargs = self.configs[kind].cmdargs
            cpppaths = self.configs[kind].cpppaths
            cppdefines = self.configs[kind].cppdefines

            env_has_buildtarget = 'MSVSBUILDTARGET' in self.env
            if not env_has_buildtarget:
                self.env['MSVSBUILDTARGET'] = buildtarget

            starting = 'echo Starting SCons && '
            if cmdargs:
                cmdargs = ' ' + cmdargs
            else:
                cmdargs = ''
            buildcmd    = xmlify(starting + self.env.subst('$MSVSBUILDCOM', 1) + cmdargs)
            rebuildcmd  = xmlify(starting + self.env.subst('$MSVSREBUILDCOM', 1) + cmdargs)
            cleancmd    = xmlify(starting + self.env.subst('$MSVSCLEANCOM', 1) + cmdargs)

            # This isn't perfect; CPPDEFINES and CPPPATH can contain $TARGET and $SOURCE,
            # so they could vary depending on the command being generated.  This code
            # assumes they don't.
            preprocdefs = xmlify(';'.join(processDefines(cppdefines)))
            includepath = xmlify(';'.join(processIncludes(cpppaths, self.env, None, None)))

            if not env_has_buildtarget:
                del self.env['MSVSBUILDTARGET']

            self.file.write(self.dspconfiguration % locals())

        self.file.write('\t</Configurations>\n')

        if self.version_num >= 7.1:
            self.file.write('\t<References>\n'
                            '\t</References>\n')

        self.PrintSourceFiles()

        self.file.write('</VisualStudioProject>\n')

        if self.nokeep == 0:
            # now we pickle some data and add it to the file -- MSDEV will ignore it.
            pdata = pickle.dumps(self.configs,PICKLE_PROTOCOL)
            pdata = base64.b64encode(pdata).decode()
            self.file.write('<!-- SCons Data:\n' + pdata + '\n')
            pdata = pickle.dumps(self.sources,PICKLE_PROTOCOL)
            pdata = base64.b64encode(pdata).decode()
            self.file.write(pdata + '-->\n')

    def printSources(self, hierarchy, commonprefix):
        sorteditems = sorted(hierarchy.items(), key=lambda a: a[0].lower())

        # First folders, then files
        for key, value in sorteditems:
            if SCons.Util.is_Dict(value):
                self.file.write('\t\t\t<Filter\n'
                                '\t\t\t\tName="%s"\n'
                                '\t\t\t\tFilter="">\n' % key)
                self.printSources(value, commonprefix)
                self.file.write('\t\t\t</Filter>\n')

        for key, value in sorteditems:
            if SCons.Util.is_String(value):
                file = value
                if commonprefix:
                    file = os.path.join(commonprefix, value)
                file = os.path.normpath(file)
                self.file.write('\t\t\t<File\n'
                                '\t\t\t\tRelativePath="%s">\n'
                                '\t\t\t</File>\n' % file)

    def PrintSourceFiles(self):
        categories = {'Source Files': 'cpp;c;cxx;l;y;def;odl;idl;hpj;bat',
                      'Header Files': 'h;hpp;hxx;hm;inl',
                      'Local Headers': 'h;hpp;hxx;hm;inl',
                      'Resource Files': 'r;rc;ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe',
                      'Other Files': ''}

        self.file.write('\t<Files>\n')

        cats = sorted([k for k in categories.keys() if self.sources[k]],
                      key=lambda a: a.lower())
        for kind in cats:
            if len(cats) > 1:
                self.file.write('\t\t<Filter\n'
                                '\t\t\tName="%s"\n'
                                '\t\t\tFilter="%s">\n' % (kind, categories[kind]))

            sources = self.sources[kind]

            # First remove any common prefix
            commonprefix = None
            s = list(map(os.path.normpath, sources))
            # take the dirname because the prefix may include parts
            # of the filenames (e.g. if you have 'dir\abcd' and
            # 'dir\acde' then the cp will be 'dir\a' )
            cp = os.path.dirname( os.path.commonprefix(s) )
            if cp and s[0][len(cp)] == os.sep:
                # +1 because the filename starts after the separator
                sources = [s[len(cp)+1:] for s in sources]
                commonprefix = cp

            hierarchy = makeHierarchy(sources)
            self.printSources(hierarchy, commonprefix=commonprefix)

            if len(cats)>1:
                self.file.write('\t\t</Filter>\n')

        # add the SConscript file outside of the groups
        self.file.write('\t\t<File\n'
                        '\t\t\tRelativePath="%s">\n'
                        '\t\t</File>\n' % str(self.sconscript))

        self.file.write('\t</Files>\n'
                        '\t<Globals>\n'
                        '\t</Globals>\n')

    def Parse(self):
        try:
            dspfile = open(self.dspabs,'r')
        except IOError:
            return # doesn't exist yet, so can't add anything to configs.

        line = dspfile.readline()
        # skip until marker
        while line:
            if '<!-- SCons Data:' in line:
                break
            line = dspfile.readline()

        # read to get configs
        line = dspfile.readline()
        datas = line
        while line and line != '\n':
            line = dspfile.readline()
            datas = datas + line

        # OK, we've found our little pickled cache of data.
        try:
            datas = base64.decodestring(datas)
            data = pickle.loads(datas)
        except KeyboardInterrupt:
            raise
        except:
            return # unable to unpickle any data for some reason

        self.configs.update(data)

        # keep reading to get sources
        data = None
        line = dspfile.readline()
        datas = line
        while line and line != '\n':
            line = dspfile.readline()
            datas = datas + line
        dspfile.close()

        # OK, we've found our little pickled cache of data.
        try:
            datas = base64.decodestring(datas)
            data = pickle.loads(datas)
        except KeyboardInterrupt:
            raise
        except:
            return # unable to unpickle any data for some reason

        self.sources.update(data)

    def Build(self):
        try:
            self.file = open(self.dspabs,'w')
        except IOError as detail:
            raise SCons.Errors.InternalError('Unable to open "' + self.dspabs + '" for writing:' + str(detail))
        else:
            self.PrintHeader()
            self.PrintProject()
            self.file.close()

        _GenerateV7User.Build(self)

V10DSPHeader = """\
<?xml version="1.0" encoding="%(encoding)s"?>
<Project DefaultTargets="Build" ToolsVersion="%(versionstr)s" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
"""

V10DSPProjectConfiguration = """\
\t\t<ProjectConfiguration Include="%(variant)s|%(platform)s">
\t\t\t<Configuration>%(variant)s</Configuration>
\t\t\t<Platform>%(platform)s</Platform>
\t\t</ProjectConfiguration>
"""

V10DSPGlobals = """\
\t<PropertyGroup Label="Globals">
\t\t<ProjectGuid>%(project_guid)s</ProjectGuid>
%(scc_attrs)s\t\t<RootNamespace>%(name)s</RootNamespace>
\t\t<Keyword>MakeFileProj</Keyword>
\t\t<VCProjectUpgraderObjectName>NoUpgrade</VCProjectUpgraderObjectName>
\t</PropertyGroup>
"""

V10DSPPropertyGroupCondition = """\
\t<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='%(variant)s|%(platform)s'" Label="Configuration">
\t\t<ConfigurationType>Makefile</ConfigurationType>
\t\t<UseOfMfc>false</UseOfMfc>
\t\t<PlatformToolset>%(toolset)s</PlatformToolset>
\t</PropertyGroup>
"""

V10DSPImportGroupCondition = """\
\t<ImportGroup Condition="'$(Configuration)|$(Platform)'=='%(variant)s|%(platform)s'" Label="PropertySheets">
\t\t<Import Project="$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
\t</ImportGroup>
"""

V10DSPCommandLine = """\
\t\t<NMakeBuildCommandLine Condition="'$(Configuration)|$(Platform)'=='%(variant)s|%(platform)s'">%(buildcmd)s</NMakeBuildCommandLine>
\t\t<NMakeReBuildCommandLine Condition="'$(Configuration)|$(Platform)'=='%(variant)s|%(platform)s'">%(rebuildcmd)s</NMakeReBuildCommandLine>
\t\t<NMakeCleanCommandLine Condition="'$(Configuration)|$(Platform)'=='%(variant)s|%(platform)s'">%(cleancmd)s</NMakeCleanCommandLine>
\t\t<NMakeOutput Condition="'$(Configuration)|$(Platform)'=='%(variant)s|%(platform)s'">%(runfile)s</NMakeOutput>
\t\t<NMakePreprocessorDefinitions Condition="'$(Configuration)|$(Platform)'=='%(variant)s|%(platform)s'">%(preprocdefs)s</NMakePreprocessorDefinitions>
\t\t<NMakeIncludeSearchPath Condition="'$(Configuration)|$(Platform)'=='%(variant)s|%(platform)s'">%(includepath)s</NMakeIncludeSearchPath>
\t\t<NMakeForcedIncludes Condition="'$(Configuration)|$(Platform)'=='%(variant)s|%(platform)s'">$(NMakeForcedIncludes)</NMakeForcedIncludes>
\t\t<NMakeAssemblySearchPath Condition="'$(Configuration)|$(Platform)'=='%(variant)s|%(platform)s'">$(NMakeAssemblySearchPath)</NMakeAssemblySearchPath>
\t\t<NMakeForcedUsingAssemblies Condition="'$(Configuration)|$(Platform)'=='%(variant)s|%(platform)s'">$(NMakeForcedUsingAssemblies)</NMakeForcedUsingAssemblies>
\t\t<AdditionalOptions Condition="'$(Configuration)|$(Platform)'=='%(variant)s|%(platform)s'">%(additionaloptions)s</AdditionalOptions>
"""

V15DSPHeader = """\
<?xml version="1.0" encoding="%(encoding)s"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
"""

class _GenerateV10DSP(_DSPGenerator, _GenerateV10User):
    """Generates a Project file for MSVS 2010"""

    def __init__(self, dspfile, source, env):
        _DSPGenerator.__init__(self, dspfile, source, env)
        self.dspheader = V10DSPHeader
        self.dspconfiguration = V10DSPProjectConfiguration
        self.dspglobals = V10DSPGlobals

        _GenerateV10User.__init__(self, dspfile, source, env)

    def PrintHeader(self):
        env = self.env
        name = self.name
        versionstr = self.versionstr
        encoding = env.subst('$MSVSENCODING')
        project_guid = env.get('MSVS_PROJECT_GUID', '')
        scc_provider = env.get('MSVS_SCC_PROVIDER', '')
        scc_project_name = env.get('MSVS_SCC_PROJECT_NAME', '')
        scc_aux_path = env.get('MSVS_SCC_AUX_PATH', '')
        # MSVS_SCC_LOCAL_PATH is kept  for backwards compatibility purpose and should
        # be deprecated as soon as possible.
        scc_local_path_legacy = env.get('MSVS_SCC_LOCAL_PATH', '')
        scc_connection_root = env.get('MSVS_SCC_CONNECTION_ROOT', os.curdir)
        scc_local_path = os.path.relpath(scc_connection_root, os.path.dirname(self.dspabs))
        if not project_guid:
            project_guid = _generateGUID(self.dspfile, '')
        if scc_provider != '':
            scc_attrs = '\t\t<SccProjectName>%s</SccProjectName>\n' % scc_project_name
            if scc_aux_path != '':
                scc_attrs += '\t\t<SccAuxPath>%s</SccAuxPath>\n' % scc_aux_path
            scc_attrs += ('\t\t<SccLocalPath>%s</SccLocalPath>\n'
                          '\t\t<SccProvider>%s</SccProvider>\n' % (scc_local_path, scc_provider))
        elif scc_local_path_legacy != '':
            # This case is kept for backwards compatibility purpose and should
            # be deprecated as soon as possible.
            scc_attrs = ('\t\t<SccProjectName>%s</SccProjectName>\n'
                         '\t\t<SccLocalPath>%s</SccLocalPath>\n' % (scc_project_name, scc_local_path_legacy))
        else:
            self.dspglobals = self.dspglobals.replace('%(scc_attrs)s', '')

        self.file.write(self.dspheader % locals())

        self.file.write('\t<ItemGroup Label="ProjectConfigurations">\n')

        confkeys = sorted(self.configs.keys())
        for kind in confkeys:
            variant = self.configs[kind].variant
            platform = self.configs[kind].platform
            self.file.write(self.dspconfiguration % locals())

        self.file.write('\t</ItemGroup>\n')

        self.file.write(self.dspglobals % locals())

    def PrintProject(self):
        name = self.name
        confkeys = sorted(self.configs.keys())

        self.file.write('\t<Import Project="$(VCTargetsPath)\\Microsoft.Cpp.Default.props" />\n')

        toolset = ''
        if 'MSVC_VERSION' in self.env:
            version_num, suite = msvs_parse_version(self.env['MSVC_VERSION'])
            toolset = 'v%d' % (version_num * 10)
        for kind in confkeys:
            variant = self.configs[kind].variant
            platform = self.configs[kind].platform
            self.file.write(V10DSPPropertyGroupCondition % locals())

        self.file.write('\t<Import Project="$(VCTargetsPath)\\Microsoft.Cpp.props" />\n')
        self.file.write('\t<ImportGroup Label="ExtensionSettings">\n')
        self.file.write('\t</ImportGroup>\n')

        for kind in confkeys:
            variant = self.configs[kind].variant
            platform = self.configs[kind].platform
            self.file.write(V10DSPImportGroupCondition % locals())

        self.file.write('\t<PropertyGroup Label="UserMacros" />\n')
        self.file.write('\t<PropertyGroup>\n')
        self.file.write('\t<_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>\n')

        for kind in confkeys:
            variant = self.configs[kind].variant
            platform = self.configs[kind].platform
            outdir = self.configs[kind].outdir
            buildtarget = self.configs[kind].buildtarget
            runfile     = self.configs[kind].runfile
            cmdargs = self.configs[kind].cmdargs
            cpppaths = self.configs[kind].cpppaths
            cppdefines = self.configs[kind].cppdefines
            cppflags = self.configs[kind].cppflags

            env_has_buildtarget = 'MSVSBUILDTARGET' in self.env
            if not env_has_buildtarget:
                self.env['MSVSBUILDTARGET'] = buildtarget

            starting = 'echo Starting SCons && '
            if cmdargs:
                cmdargs = ' ' + cmdargs
            else:
                cmdargs = ''
            buildcmd    = xmlify(starting + self.env.subst('$MSVSBUILDCOM', 1) + cmdargs)
            rebuildcmd  = xmlify(starting + self.env.subst('$MSVSREBUILDCOM', 1) + cmdargs)
            cleancmd    = xmlify(starting + self.env.subst('$MSVSCLEANCOM', 1) + cmdargs)

            # This isn't perfect; CPPDEFINES and CPPPATH can contain $TARGET and $SOURCE,
            # so they could vary depending on the command being generated.  This code
            # assumes they don't.
            preprocdefs = xmlify(';'.join(processDefines(cppdefines)))
            includepath = xmlify(';'.join(processIncludes(cpppaths, self.env, None, None)))
            additionaloptions = xmlify(' '.join(processFlags(cppflags, self.env)))

            if not env_has_buildtarget:
                del self.env['MSVSBUILDTARGET']

            self.file.write(V10DSPCommandLine % locals())

        self.file.write('\t</PropertyGroup>\n')

        #filter settings in MSVS 2010 are stored in separate file
        self.filtersabs = self.dspabs + '.filters'
        try:
            self.filters_file = open(self.filtersabs, 'w')
        except IOError as detail:
            raise SCons.Errors.InternalError('Unable to open "' + self.filtersabs + '" for writing:' + str(detail))

        self.filters_file.write('<?xml version="1.0" encoding="utf-8"?>\n'
                                '<Project ToolsVersion="%s" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\n' %
                                self.versionstr)

        self.PrintSourceFiles()

        self.filters_file.write('</Project>')
        self.filters_file.close()

        self.file.write('\t<Import Project="$(VCTargetsPath)\\Microsoft.Cpp.targets" />\n'
                        '\t<ImportGroup Label="ExtensionTargets">\n'
                        '\t</ImportGroup>\n'
                        '</Project>\n')

        if self.nokeep == 0:
            # now we pickle some data and add it to the file -- MSDEV will ignore it.
            pdata = pickle.dumps(self.configs,PICKLE_PROTOCOL)
            pdata = base64.b64encode(pdata).decode()
            self.file.write('<!-- SCons Data:\n' + pdata + '\n')
            pdata = pickle.dumps(self.sources,PICKLE_PROTOCOL)
            pdata = base64.b64encode(pdata).decode()
            self.file.write(pdata + '-->\n')

    def printFilters(self, hierarchy, name):
        sorteditems = sorted(hierarchy.items(), key = lambda a: a[0].lower())

        for key, value in sorteditems:
            if SCons.Util.is_Dict(value):
                filter_name = name + '\\' + key
                self.filters_file.write('\t\t<Filter Include="%s">\n'
                                        '\t\t\t<UniqueIdentifier>%s</UniqueIdentifier>\n'
                                        '\t\t</Filter>\n' % (filter_name, _generateGUID(self.dspabs, filter_name)))
                self.printFilters(value, filter_name)

    def printSources(self, hierarchy, kind, commonprefix, filter_name):
        keywords = {'Source Files': 'ClCompile',
                    'Header Files': 'ClInclude',
                    'Local Headers': 'ClInclude',
                    'Resource Files': 'None',
                    'Other Files': 'None'}

        sorteditems = sorted(hierarchy.items(), key = lambda a: a[0].lower())

        # First folders, then files
        for key, value in sorteditems:
            if SCons.Util.is_Dict(value):
                self.printSources(value, kind, commonprefix, filter_name + '\\' + key)

        for key, value in sorteditems:
            if SCons.Util.is_String(value):
                file = value
                if commonprefix:
                    file = os.path.join(commonprefix, value)
                file = os.path.normpath(file)

                self.file.write('\t\t<%s Include="%s" />\n' % (keywords[kind], file))
                self.filters_file.write('\t\t<%s Include="%s">\n'
                                        '\t\t\t<Filter>%s</Filter>\n'
                                        '\t\t</%s>\n' % (keywords[kind], file, filter_name, keywords[kind]))

    def PrintSourceFiles(self):
        categories = {'Source Files': 'cpp;c;cxx;l;y;def;odl;idl;hpj;bat',
                      'Header Files': 'h;hpp;hxx;hm;inl',
                      'Local Headers': 'h;hpp;hxx;hm;inl',
                      'Resource Files': 'r;rc;ico;cur;bmp;dlg;rc2;rct;bin;cnt;rtf;gif;jpg;jpeg;jpe',
                      'Other Files': ''}

        cats = sorted([k for k in categories.keys() if self.sources[k]],
                      key = lambda a: a.lower())

        # print vcxproj.filters file first
        self.filters_file.write('\t<ItemGroup>\n')
        for kind in cats:
            self.filters_file.write('\t\t<Filter Include="%s">\n'
                                    '\t\t\t<UniqueIdentifier>{7b42d31d-d53c-4868-8b92-ca2bc9fc052f}</UniqueIdentifier>\n'
                                    '\t\t\t<Extensions>%s</Extensions>\n'
                                    '\t\t</Filter>\n' % (kind, categories[kind]))

            # First remove any common prefix
            sources = self.sources[kind]
            commonprefix = None
            s = list(map(os.path.normpath, sources))
            # take the dirname because the prefix may include parts
            # of the filenames (e.g. if you have 'dir\abcd' and
            # 'dir\acde' then the cp will be 'dir\a' )
            cp = os.path.dirname( os.path.commonprefix(s) )
            if cp and s[0][len(cp)] == os.sep:
                # +1 because the filename starts after the separator
                sources = [s[len(cp)+1:] for s in sources]
                commonprefix = cp

            hierarchy = makeHierarchy(sources)
            self.printFilters(hierarchy, kind)

        self.filters_file.write('\t</ItemGroup>\n')

        # then print files and filters
        for kind in cats:
            self.file.write('\t<ItemGroup>\n')
            self.filters_file.write('\t<ItemGroup>\n')

            # First remove any common prefix
            sources = self.sources[kind]
            commonprefix = None
            s = list(map(os.path.normpath, sources))
            # take the dirname because the prefix may include parts
            # of the filenames (e.g. if you have 'dir\abcd' and
            # 'dir\acde' then the cp will be 'dir\a' )
            cp = os.path.dirname( os.path.commonprefix(s) )
            if cp and s[0][len(cp)] == os.sep:
                # +1 because the filename starts after the separator
                sources = [s[len(cp)+1:] for s in sources]
                commonprefix = cp

            hierarchy = makeHierarchy(sources)
            self.printSources(hierarchy, kind, commonprefix, kind)

            self.file.write('\t</ItemGroup>\n')
            self.filters_file.write('\t</ItemGroup>\n')

        # add the SConscript file outside of the groups
        self.file.write('\t<ItemGroup>\n'
                        '\t\t<None Include="%s" />\n'
                        #'\t\t<None Include="SConstruct" />\n'
                        '\t</ItemGroup>\n' % str(self.sconscript))

    def Parse(self):
        # DEBUG
        # print("_GenerateV10DSP.Parse()")
        pass

    def Build(self):
        try:
            self.file = open(self.dspabs, 'w')
        except IOError as detail:
            raise SCons.Errors.InternalError('Unable to open "' + self.dspabs + '" for writing:' + str(detail))
        else:
            self.PrintHeader()
            self.PrintProject()
            self.file.close()

        _GenerateV10User.Build(self)

class _DSWGenerator:
    """ Base class for DSW generators """
    def __init__(self, dswfile, source, env):
        self.dswfile = os.path.normpath(str(dswfile))
        self.dsw_folder_path = os.path.dirname(os.path.abspath(self.dswfile))
        self.env = env

        if 'projects' not in env:
            raise SCons.Errors.UserError("You must specify a 'projects' argument to create an MSVSSolution.")
        projects = env['projects']
        if not SCons.Util.is_List(projects):
            raise SCons.Errors.InternalError("The 'projects' argument must be a list of nodes.")
        projects = SCons.Util.flatten(projects)
        if len(projects) < 1:
            raise SCons.Errors.UserError("You must specify at least one project to create an MSVSSolution.")
        self.dspfiles = list(map(str, projects))

        if 'name' in self.env:
            self.name = self.env['name']
        else:
            self.name = os.path.basename(SCons.Util.splitext(self.dswfile)[0])
        self.name = self.env.subst(self.name)

    def Build(self):
        pass

class _GenerateV7DSW(_DSWGenerator):
    """Generates a Solution file for MSVS .NET"""
    def __init__(self, dswfile, source, env):
        super().__init__(dswfile, source, env)

        self.file = None
        self.version = self.env['MSVS_VERSION']
        self.version_num, self.suite = msvs_parse_version(self.version)
        self.versionstr = '7.00'
        if self.version_num >= 11.0:
            self.versionstr = '12.00'
        elif self.version_num >= 10.0:
            self.versionstr = '11.00'
        elif self.version_num >= 9.0:
            self.versionstr = '10.00'
        elif self.version_num >= 8.0:
            self.versionstr = '9.00'
        elif self.version_num >= 7.1:
            self.versionstr = '8.00'

        if 'slnguid' in env and env['slnguid']:
            self.slnguid = env['slnguid']
        else:
            self.slnguid = _generateGUID(dswfile, self.name)

        self.configs = {}

        self.nokeep = 0
        if 'nokeep' in env and env['variant'] != 0:
            self.nokeep = 1

        if self.nokeep == 0 and os.path.exists(self.dswfile):
            self.Parse()

        def AddConfig(self, variant, dswfile=dswfile):
            config = Config()

            match = re.match(r'(.*)\|(.*)', variant)
            if match:
                config.variant = match.group(1)
                config.platform = match.group(2)
            else:
                config.variant = variant
                config.platform = 'Win32'

            self.configs[variant] = config
            # DEBUG
            # print("Adding '" + self.name + ' - ' + config.variant + '|' + config.platform + "' to '" + str(dswfile) + "'")

        if 'variant' not in env:
            raise SCons.Errors.InternalError("You must specify a 'variant' argument (i.e. 'Debug' or " +\
                  "'Release') to create an MSVS Solution File.")
        elif SCons.Util.is_String(env['variant']):
            AddConfig(self, env['variant'])
        elif SCons.Util.is_List(env['variant']):
            for variant in env['variant']:
                AddConfig(self, variant)

        seen = set()
        self.platforms = [p.platform for p in self.configs.values()
                          if not (p.platform in seen or seen.add(p.platform))]

        def GenerateProjectFilesInfo(self):
            for dspfile in self.dspfiles:
                dsp_folder_path, name = os.path.split(dspfile)
                dsp_folder_path = os.path.abspath(dsp_folder_path)
                if SCons.Util.splitext(name)[1] == '.filters':
                    # Ignore .filters project files
                    continue
                dsp_relative_folder_path = os.path.relpath(dsp_folder_path, self.dsw_folder_path)
                if dsp_relative_folder_path == os.curdir:
                    dsp_relative_file_path = name
                else:
                    dsp_relative_file_path = os.path.join(dsp_relative_folder_path, name)
                dspfile_info = {'NAME': name,
                                'GUID': _generateGUID(dspfile, ''),
                                'FOLDER_PATH': dsp_folder_path,
                                'FILE_PATH': dspfile,
                                'SLN_RELATIVE_FOLDER_PATH': dsp_relative_folder_path,
                                'SLN_RELATIVE_FILE_PATH': dsp_relative_file_path}
                self.dspfiles_info.append(dspfile_info)

        self.dspfiles_info = []
        GenerateProjectFilesInfo(self)

    def Parse(self):
        try:
            dswfile = open(self.dswfile,'r')
        except IOError:
            return # doesn't exist yet, so can't add anything to configs.

        line = dswfile.readline()
        while line:
            if line[:9] == "EndGlobal":
                break
            line = dswfile.readline()

        line = dswfile.readline()
        datas = line
        while line:
            line = dswfile.readline()
            datas = datas + line
        dswfile.close()

        # OK, we've found our little pickled cache of data.
        try:
            datas = base64.decodestring(datas)
            data = pickle.loads(datas)
        except KeyboardInterrupt:
            raise
        except:
            return # unable to unpickle any data for some reason

        self.configs.update(data)

    def PrintSolution(self):
        """Writes a solution file"""
        self.file.write('Microsoft Visual Studio Solution File, Format Version %s\n' % self.versionstr)
        if self.version_num >= 14.3:
            # Visual Studio 2022 is considered to be version 17.
            self.file.write('# Visual Studio 17\n')
        elif self.version_num >= 14.2:
            # Visual Studio 2019 is considered to be version 16.
            self.file.write('# Visual Studio 16\n')
        elif self.version_num > 14.0:
            # Visual Studio 2015 and 2017 are both considered to be version 15.
            self.file.write('# Visual Studio 15\n')
        elif self.version_num >= 12.0:
            self.file.write('# Visual Studio 14\n')
        elif self.version_num >= 11.0:
            self.file.write('# Visual Studio 11\n')
        elif self.version_num >= 10.0:
            self.file.write('# Visual Studio 2010\n')
        elif self.version_num >= 9.0:
            self.file.write('# Visual Studio 2008\n')
        elif self.version_num >= 8.0:
            self.file.write('# Visual Studio 2005\n')

        for dspinfo in self.dspfiles_info:
            name = dspinfo['NAME']
            base, suffix = SCons.Util.splitext(name)
            if suffix == '.vcproj':
                name = base
            self.file.write('Project("%s") = "%s", "%s", "%s"\n'
                            % (external_makefile_guid, name, dspinfo['SLN_RELATIVE_FILE_PATH'], dspinfo['GUID']))
            if 7.1 <= self.version_num < 8.0:
                self.file.write('\tProjectSection(ProjectDependencies) = postProject\n'
                                '\tEndProjectSection\n')
            self.file.write('EndProject\n')

        self.file.write('Global\n')

        env = self.env
        if 'MSVS_SCC_PROVIDER' in env:
            scc_number_of_projects = len(self.dspfiles) + 1
            slnguid = self.slnguid
            scc_provider = env.get('MSVS_SCC_PROVIDER', '').replace(' ', r'\u0020')
            scc_project_name = env.get('MSVS_SCC_PROJECT_NAME', '').replace(' ', r'\u0020')
            scc_connection_root = env.get('MSVS_SCC_CONNECTION_ROOT', os.curdir)
            scc_local_path = os.path.relpath(scc_connection_root, self.dsw_folder_path).replace('\\', '\\\\')
            self.file.write('\tGlobalSection(SourceCodeControl) = preSolution\n'
                            '\t\tSccNumberOfProjects = %(scc_number_of_projects)d\n'
                            '\t\tSccProjectName0 = %(scc_project_name)s\n'
                            '\t\tSccLocalPath0 = %(scc_local_path)s\n'
                            '\t\tSccProvider0 = %(scc_provider)s\n'
                            '\t\tCanCheckoutShared = true\n'  % locals())
            sln_relative_path_from_scc = os.path.relpath(self.dsw_folder_path, scc_connection_root)
            if sln_relative_path_from_scc != os.curdir:
                self.file.write('\t\tSccProjectFilePathRelativizedFromConnection0 = %s\\\\\n'
                                % sln_relative_path_from_scc.replace('\\', '\\\\'))
            if self.version_num < 8.0:
                # When present, SolutionUniqueID is automatically removed by VS 2005
                # TODO: check for Visual Studio versions newer than 2005
                self.file.write('\t\tSolutionUniqueID = %s\n' % slnguid)
            for dspinfo in self.dspfiles_info:
                i = self.dspfiles_info.index(dspinfo) + 1
                dsp_relative_file_path = dspinfo['SLN_RELATIVE_FILE_PATH'].replace('\\', '\\\\')
                dsp_scc_relative_folder_path = os.path.relpath(dspinfo['FOLDER_PATH'], scc_connection_root).replace('\\', '\\\\')
                self.file.write('\t\tSccProjectUniqueName%(i)s = %(dsp_relative_file_path)s\n'
                                '\t\tSccLocalPath%(i)d = %(scc_local_path)s\n'
                                '\t\tCanCheckoutShared = true\n'
                                '\t\tSccProjectFilePathRelativizedFromConnection%(i)s = %(dsp_scc_relative_folder_path)s\\\\\n'
                                % locals())
            self.file.write('\tEndGlobalSection\n')
        if self.version_num >= 8.0:
            self.file.write('\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\n')
        else:
            self.file.write('\tGlobalSection(SolutionConfiguration) = preSolution\n')

        confkeys = sorted(self.configs.keys())
        cnt = 0
        for name in confkeys:
            variant = self.configs[name].variant
            platform = self.configs[name].platform
            if self.version_num >= 8.0:
                self.file.write('\t\t%s|%s = %s|%s\n' % (variant, platform, variant, platform))
            else:
                self.file.write('\t\tConfigName.%d = %s\n' % (cnt, variant))
            cnt = cnt + 1
        self.file.write('\tEndGlobalSection\n')
        if self.version_num <= 7.1:
            self.file.write('\tGlobalSection(ProjectDependencies) = postSolution\n'
                            '\tEndGlobalSection\n')
        if self.version_num >= 8.0:
            self.file.write('\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\n')
        else:
            self.file.write('\tGlobalSection(ProjectConfiguration) = postSolution\n')

        for name in confkeys:
            variant = self.configs[name].variant
            platform = self.configs[name].platform
            if self.version_num >= 8.0:
                for dspinfo in self.dspfiles_info:
                    guid = dspinfo['GUID']
                    self.file.write('\t\t%s.%s|%s.ActiveCfg = %s|%s\n'
                                    '\t\t%s.%s|%s.Build.0 = %s|%s\n'  % (guid,variant,platform,variant,platform,guid,variant,platform,variant,platform))
            else:
                for dspinfo in self.dspfiles_info:
                    guid = dspinfo['GUID']
                    self.file.write('\t\t%s.%s.ActiveCfg = %s|%s\n'
                                    '\t\t%s.%s.Build.0 = %s|%s\n'  %(guid,variant,variant,platform,guid,variant,variant,platform))

        self.file.write('\tEndGlobalSection\n')

        if self.version_num >= 8.0:
            self.file.write('\tGlobalSection(SolutionProperties) = preSolution\n'
                            '\t\tHideSolutionNode = FALSE\n'
                            '\tEndGlobalSection\n')
        else:
            self.file.write('\tGlobalSection(ExtensibilityGlobals) = postSolution\n'
                            '\tEndGlobalSection\n'
                            '\tGlobalSection(ExtensibilityAddIns) = postSolution\n'
                            '\tEndGlobalSection\n')
        self.file.write('EndGlobal\n')
        if self.nokeep == 0:
            pdata = pickle.dumps(self.configs,PICKLE_PROTOCOL)
            pdata = base64.b64encode(pdata).decode()
            self.file.write(pdata)
            self.file.write('\n')

    def Build(self):
        try:
            self.file = open(self.dswfile,'w')
        except IOError as detail:
            raise SCons.Errors.InternalError('Unable to open "' + self.dswfile + '" for writing:' + str(detail))
        else:
            self.PrintSolution()
            self.file.close()

V6DSWHeader = """\
Microsoft Developer Studio Workspace File, Format Version 6.00
# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!

###############################################################################

Project: "%(name)s"="%(dspfile)s" - Package Owner=<4>

Package=<5>
{{{
}}}

Package=<4>
{{{
}}}

###############################################################################

Global:

Package=<5>
{{{
}}}

Package=<3>
{{{
}}}

###############################################################################
"""

class _GenerateV6DSW(_DSWGenerator):
    """Generates a Workspace file for MSVS 6.0"""

    def PrintWorkspace(self):
        """ writes a DSW file """
        name = self.name
        dspfile = os.path.relpath(self.dspfiles[0], self.dsw_folder_path)
        self.file.write(V6DSWHeader % locals())

    def Build(self):
        try:
            self.file = open(self.dswfile,'w')
        except IOError as detail:
            raise SCons.Errors.InternalError('Unable to open "' + self.dswfile + '" for writing:' + str(detail))
        else:
            self.PrintWorkspace()
            self.file.close()


def GenerateDSP(dspfile, source, env):
    """Generates a Project file based on the version of MSVS that is being used"""

    version_num = 6.0
    if 'MSVS_VERSION' in env:
        version_num, suite = msvs_parse_version(env['MSVS_VERSION'])
    if version_num >= 10.0:
        g = _GenerateV10DSP(dspfile, source, env)
        g.Build()
    elif version_num >= 7.0:
        g = _GenerateV7DSP(dspfile, source, env)
        g.Build()
    else:
        g = _GenerateV6DSP(dspfile, source, env)
        g.Build()

def GenerateDSW(dswfile, source, env):
    """Generates a Solution/Workspace file based on the version of MSVS that is being used"""

    version_num = 6.0
    if 'MSVS_VERSION' in env:
        version_num, suite = msvs_parse_version(env['MSVS_VERSION'])
    if version_num >= 7.0:
        g = _GenerateV7DSW(dswfile, source, env)
        g.Build()
    else:
        g = _GenerateV6DSW(dswfile, source, env)
        g.Build()


##############################################################################
# Above here are the classes and functions for generation of
# DSP/DSW/SLN/VCPROJ files.
##############################################################################

def GetMSVSProjectSuffix(target, source, env, for_signature):
    return env['MSVS']['PROJECTSUFFIX']

def GetMSVSSolutionSuffix(target, source, env, for_signature):
    return env['MSVS']['SOLUTIONSUFFIX']

def GenerateProject(target, source, env):
    # generate the dsp file, according to the version of MSVS.
    builddspfile = target[0]
    dspfile = builddspfile.srcnode()

    # this detects whether or not we're using a VariantDir
    if dspfile is not builddspfile:
        try:
            bdsp = open(str(builddspfile), "w+")
        except IOError as detail:
            print('Unable to open "' + str(dspfile) + '" for writing:',detail,'\n')
            raise

        bdsp.write("This is just a placeholder file.\nThe real project file is here:\n%s\n" % dspfile.get_abspath())
        bdsp.close()

    GenerateDSP(dspfile, source, env)

    if env.get('auto_build_solution', 1):
        builddswfile = target[1]
        dswfile = builddswfile.srcnode()

        if dswfile is not builddswfile:

            try:
                bdsw = open(str(builddswfile), "w+")
            except IOError as detail:
                print('Unable to open "' + str(dspfile) + '" for writing:',detail,'\n')
                raise

            bdsw.write("This is just a placeholder file.\nThe real workspace file is here:\n%s\n" % dswfile.get_abspath())
            bdsw.close()

        GenerateDSW(dswfile, source, env)

def GenerateSolution(target, source, env):
    GenerateDSW(target[0], source, env)

def projectEmitter(target, source, env):
    """Sets up the DSP dependencies."""

    # todo: Not sure what sets source to what user has passed as target,
    # but this is what happens. When that is fixed, we also won't have
    # to make the user always append env['MSVSPROJECTSUFFIX'] to target.
    if source[0] == target[0]:
        source = []

    # make sure the suffix is correct for the version of MSVS we're running.
    (base, suff) = SCons.Util.splitext(str(target[0]))
    suff = env.subst('$MSVSPROJECTSUFFIX')
    target[0] = base + suff

    if not source:
        source = 'prj_inputs:'
        source = source + env.subst('$MSVSSCONSCOM', 1)
        source = source + env.subst('$MSVSENCODING', 1)

        # Project file depends on CPPDEFINES and CPPPATH
        preprocdefs = xmlify(';'.join(processDefines(env.get('CPPDEFINES', []))))
        includepath = xmlify(';'.join(processIncludes(env.get('CPPPATH', []), env, None, None)))
        source = source + "; ppdefs:%s incpath:%s"%(preprocdefs, includepath)

        if 'buildtarget' in env and env['buildtarget'] is not None:
            if SCons.Util.is_String(env['buildtarget']):
                source = source + ' "%s"' % env['buildtarget']
            elif SCons.Util.is_List(env['buildtarget']):
                for bt in env['buildtarget']:
                    if SCons.Util.is_String(bt):
                        source = source + ' "%s"' % bt
                    else:
                        try: source = source + ' "%s"' % bt.get_abspath()
                        except AttributeError: raise SCons.Errors.InternalError("buildtarget can be a string, a node, a list of strings or nodes, or None")
            else:
                try: source = source + ' "%s"' % env['buildtarget'].get_abspath()
                except AttributeError: raise SCons.Errors.InternalError("buildtarget can be a string, a node, a list of strings or nodes, or None")

        if 'outdir' in env and env['outdir'] is not None:
            if SCons.Util.is_String(env['outdir']):
                source = source + ' "%s"' % env['outdir']
            elif SCons.Util.is_List(env['outdir']):
                for s in env['outdir']:
                    if SCons.Util.is_String(s):
                        source = source + ' "%s"' % s
                    else:
                        try: source = source + ' "%s"' % s.get_abspath()
                        except AttributeError: raise SCons.Errors.InternalError("outdir can be a string, a node, a list of strings or nodes, or None")
            else:
                try: source = source + ' "%s"' % env['outdir'].get_abspath()
                except AttributeError: raise SCons.Errors.InternalError("outdir can be a string, a node, a list of strings or nodes, or None")

        if 'name' in env:
            if SCons.Util.is_String(env['name']):
                source = source + ' "%s"' % env['name']
            else:
                raise SCons.Errors.InternalError("name must be a string")

        if 'variant' in env:
            if SCons.Util.is_String(env['variant']):
                source = source + ' "%s"' % env['variant']
            elif SCons.Util.is_List(env['variant']):
                for variant in env['variant']:
                    if SCons.Util.is_String(variant):
                        source = source + ' "%s"' % variant
                    else:
                        raise SCons.Errors.InternalError("name must be a string or a list of strings")
            else:
                raise SCons.Errors.InternalError("variant must be a string or a list of strings")
        else:
            raise SCons.Errors.InternalError("variant must be specified")

        for s in _DSPGenerator.srcargs:
            if s in env:
                if SCons.Util.is_String(env[s]):
                    source = source + ' "%s' % env[s]
                elif SCons.Util.is_List(env[s]):
                    for t in env[s]:
                        if SCons.Util.is_String(t):
                            source = source + ' "%s"' % t
                        else:
                            raise SCons.Errors.InternalError(s + " must be a string or a list of strings")
                else:
                    raise SCons.Errors.InternalError(s + " must be a string or a list of strings")

        source = source + ' "%s"' % str(target[0])
        source = [SCons.Node.Python.Value(source)]

    targetlist = [target[0]]
    sourcelist = source

    if env.get('auto_build_solution', 1):
        env['projects'] = [env.File(t).srcnode() for t in targetlist]
        t, s = solutionEmitter(target, target, env)
        targetlist = targetlist + t

    # Beginning with Visual Studio 2010 for each project file (.vcxproj) we have additional file (.vcxproj.filters)
    version_num = 6.0
    if 'MSVS_VERSION' in env:
        version_num, suite = msvs_parse_version(env['MSVS_VERSION'])
    if version_num >= 10.0:
        targetlist.append(targetlist[0] + '.filters')

    return (targetlist, sourcelist)

def solutionEmitter(target, source, env):
    """Sets up the DSW dependencies."""

    # todo: Not sure what sets source to what user has passed as target,
    # but this is what happens. When that is fixed, we also won't have
    # to make the user always append env['MSVSSOLUTIONSUFFIX'] to target.
    if source[0] == target[0]:
        source = []

    # make sure the suffix is correct for the version of MSVS we're running.
    (base, suff) = SCons.Util.splitext(str(target[0]))
    suff = env.subst('$MSVSSOLUTIONSUFFIX')
    target[0] = base + suff

    if not source:
        source = 'sln_inputs:'

        if 'name' in env:
            if SCons.Util.is_String(env['name']):
                source = source + ' "%s"' % env['name']
            else:
                raise SCons.Errors.InternalError("name must be a string")

        if 'variant' in env:
            if SCons.Util.is_String(env['variant']):
                source = source + ' "%s"' % env['variant']
            elif SCons.Util.is_List(env['variant']):
                for variant in env['variant']:
                    if SCons.Util.is_String(variant):
                        source = source + ' "%s"' % variant
                    else:
                        raise SCons.Errors.InternalError("name must be a string or a list of strings")
            else:
                raise SCons.Errors.InternalError("variant must be a string or a list of strings")
        else:
            raise SCons.Errors.InternalError("variant must be specified")

        if 'slnguid' in env:
            if SCons.Util.is_String(env['slnguid']):
                source = source + ' "%s"' % env['slnguid']
            else:
                raise SCons.Errors.InternalError("slnguid must be a string")

        if 'projects' in env:
            if SCons.Util.is_String(env['projects']):
                source = source + ' "%s"' % env['projects']
            elif SCons.Util.is_List(env['projects']):
                for t in env['projects']:
                    if SCons.Util.is_String(t):
                        source = source + ' "%s"' % t

        source = source + ' "%s"' % str(target[0])
        source = [SCons.Node.Python.Value(source)]

    return ([target[0]], source)

projectAction = SCons.Action.Action(GenerateProject, None)

solutionAction = SCons.Action.Action(GenerateSolution, None)

projectBuilder = SCons.Builder.Builder(action = '$MSVSPROJECTCOM',
                                       suffix = '$MSVSPROJECTSUFFIX',
                                       emitter = projectEmitter)

solutionBuilder = SCons.Builder.Builder(action = '$MSVSSOLUTIONCOM',
                                        suffix = '$MSVSSOLUTIONSUFFIX',
                                        emitter = solutionEmitter)

default_MSVS_SConscript = None

def generate(env):
    """Add Builders and construction variables for Microsoft Visual
    Studio project files to an Environment."""
    try:
        env['BUILDERS']['MSVSProject']
    except KeyError:
        env['BUILDERS']['MSVSProject'] = projectBuilder

    try:
        env['BUILDERS']['MSVSSolution']
    except KeyError:
        env['BUILDERS']['MSVSSolution'] = solutionBuilder

    env['MSVSPROJECTCOM'] = projectAction
    env['MSVSSOLUTIONCOM'] = solutionAction

    if SCons.Script.call_stack:
        # XXX Need to find a way to abstract this; the build engine
        # shouldn't depend on anything in SCons.Script.
        env['MSVSSCONSCRIPT'] = SCons.Script.call_stack[0].sconscript
    else:
        global default_MSVS_SConscript
        if default_MSVS_SConscript is None:
            default_MSVS_SConscript = env.File('SConstruct')
        env['MSVSSCONSCRIPT'] = default_MSVS_SConscript

    # Allow consumers to provide their own versions of MSVSSCONS and
    # MSVSSCONSFLAGS. This helps support consumers who use wrapper scripts to
    # invoke scons.
    if 'MSVSSCONS' not in env:
        env['MSVSSCONS'] = '"%s" -c "%s"' % (python_executable, getExecScriptMain(env))
    if 'MSVSSCONSFLAGS' not in env:
        env['MSVSSCONSFLAGS'] = '-C "${MSVSSCONSCRIPT.dir.get_abspath()}" -f ${MSVSSCONSCRIPT.name}'

    env['MSVSSCONSCOM'] = '$MSVSSCONS $MSVSSCONSFLAGS'
    env['MSVSBUILDCOM'] = '$MSVSSCONSCOM "$MSVSBUILDTARGET"'
    env['MSVSREBUILDCOM'] = '$MSVSSCONSCOM "$MSVSBUILDTARGET"'
    env['MSVSCLEANCOM'] = '$MSVSSCONSCOM -c "$MSVSBUILDTARGET"'

    # Set-up ms tools paths for default version
    msvc_setup_env_once(env, tool=tool_name)

    if 'MSVS_VERSION' in env:
        version_num, suite = msvs_parse_version(env['MSVS_VERSION'])
    else:
        (version_num, suite) = (7.0, None) # guess at a default
    if 'MSVS' not in env:
        env['MSVS'] = {}
    if version_num < 7.0:
        env['MSVS']['PROJECTSUFFIX']  = '.dsp'
        env['MSVS']['SOLUTIONSUFFIX'] = '.dsw'
    elif version_num < 10.0:
        env['MSVS']['PROJECTSUFFIX']  = '.vcproj'
        env['MSVS']['SOLUTIONSUFFIX'] = '.sln'
    else:
        env['MSVS']['PROJECTSUFFIX']  = '.vcxproj'
        env['MSVS']['SOLUTIONSUFFIX'] = '.sln'

    if version_num >= 10.0:
        env['MSVSENCODING'] = 'utf-8'
    else:
        env['MSVSENCODING'] = 'Windows-1252'

    env['GET_MSVSPROJECTSUFFIX']  = GetMSVSProjectSuffix
    env['GET_MSVSSOLUTIONSUFFIX']  = GetMSVSSolutionSuffix
    env['MSVSPROJECTSUFFIX']  = '${GET_MSVSPROJECTSUFFIX}'
    env['MSVSSOLUTIONSUFFIX']  = '${GET_MSVSSOLUTIONSUFFIX}'
    env['SCONS_HOME'] = os.environ.get('SCONS_HOME')

def exists(env):
    return msvc_setup_env_tool(env, tool=tool_name)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/msvsTests.py

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import os
import sys
import unittest
import copy

import TestCmd
import TestUnit

from SCons.Tool.msvs import *
from SCons.Tool.MSCommon.vs import SupportedVSList
import SCons.Node.FS
import SCons.Util
import SCons.Warnings

from SCons.Tool.MSCommon.common import debug

from SCons.Tool.MSCommon import get_default_version, \
                                query_versions
from SCons.Tool.msvs import _GenerateV6DSP, _GenerateV7DSP, _GenerateV10DSP

regdata_6a = r'''[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio]
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\6.0]
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\6.0\ServicePacks]
"sp3"=""
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\6.0\Setup]
"VsCommonDir"="C:\Program Files\Microsoft Visual Studio\Common"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\6.0\Setup\Microsoft Developer Network Library - Visual Studio 6.0a]
"ProductDir"="C:\Program Files\Microsoft Visual Studio\MSDN98\98VSa\1033"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\6.0\Setup\Microsoft Visual C++]
"ProductDir"="C:\Program Files\Microsoft Visual Studio\VC98"
'''.split('\n')

regdata_6b = r'''[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio]
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\6.0]
"InstallDir"="C:\VS6\Common\IDE\IDE98"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\6.0\ServicePacks]
"sp5"=""
"latest"=dword:00000005
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\6.0\Setup]
"VsCommonDir"="C:\VS6\Common"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\6.0\Setup\Microsoft Visual Basic]
"ProductDir"="C:\VS6\VB98"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\6.0\Setup\Microsoft Visual C++]
"ProductDir"="C:\VS6\VC98"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\6.0\Setup\Microsoft Visual Studio]
"ProductDir"="C:\VS6"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\6.0\Setup\Microsoft VSEE Client]
"ProductDir"="C:\VS6\Common\Tools"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\6.0\Setup\Visual Studio 98]
'''.split('\n')

regdata_7 = r'''
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0]
"InstallDir"="C:\Program Files\Microsoft Visual Studio .NET\Common7\IDE\"
"Source Directories"="C:\Program Files\Microsoft Visual Studio .NET\Vc7\crt\;C:\Program Files\Microsoft Visual Studio .NET\Vc7\atlmfc\src\mfc\;C:\Program Files\Microsoft Visual Studio .NET\Vc7\atlmfc\src\atl\"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\InstalledProducts]
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\InstalledProducts\CrystalReports]
@="#15007"
"Package"="{F05E92C6-8346-11D3-B4AD-00A0C9B04E7B}"
"ProductDetails"="#15009"
"LogoID"="0"
"PID"="#15008"
"UseInterface"=dword:00000001
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\InstalledProducts\Visual Basic.NET]
@=""
"DefaultProductAttribute"="VB"
"Package"="{164B10B9-B200-11D0-8C61-00A0C91E29D5}"
"UseInterface"=dword:00000001
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\InstalledProducts\Visual C#]
@=""
"Package"="{FAE04EC1-301F-11d3-BF4B-00C04F79EFBC}"
"UseInterface"=dword:00000001
"DefaultProductAttribute"="C#"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\InstalledProducts\VisualC++]
"UseInterface"=dword:00000001
"Package"="{F1C25864-3097-11D2-A5C5-00C04F7968B4}"
"DefaultProductAttribute"="VC"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\Setup]
"Dbghelp_path"="C:\Program Files\Microsoft Visual Studio .NET\Common7\IDE\"
"dw_dir"="C:\Program Files\Microsoft Visual Studio .NET\Common7\IDE\"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\Setup\MSDN]
"ProductDir"="C:\Program Files\Microsoft Visual Studio .NET\Msdn\1033\"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\Setup\Servicing\SKU]
"Visual Studio .NET Professional - English"="{D0610409-7D65-11D5-A54F-0090278A1BB8}"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\Setup\VB]
"ProductDir"="C:\Program Files\Microsoft Visual Studio .NET\Vb7\"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\Setup\VC]
"ProductDir"="C:\Program Files\Microsoft Visual Studio .NET\Vc7\"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\Setup\VC#]
"ProductDir"="C:\Program Files\Microsoft Visual Studio .NET\VC#\"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\Setup\Visual Studio .NET Professional - English]
"InstallSuccess"=dword:00000001
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\Setup\VS]
"EnvironmentDirectory"="C:\Program Files\Microsoft Visual Studio .NET\Common7\IDE\"
"EnvironmentPath"="C:\Program Files\Microsoft Visual Studio .NET\Common7\IDE\devenv.exe"
"VS7EnvironmentLocation"="C:\Program Files\Microsoft Visual Studio .NET\Common7\IDE\devenv.exe"
"MSMDir"="C:\Program Files\Common Files\Merge Modules\"
"ProductDir"="C:\Program Files\Microsoft Visual Studio .NET\"
"VS7CommonBinDir"="C:\Program Files\Microsoft Visual Studio .NET\Common7\Tools\"
"VS7CommonDir"="C:\Program Files\Microsoft Visual Studio .NET\Common7\"
"VSUpdateDir"="C:\Program Files\Microsoft Visual Studio .NET\Setup\VSUpdate\"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\Setup\VS\BuildNumber]
"1033"="7.0.9466"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\Setup\VS\Pro]
"ProductDir"="C:\Program Files\Microsoft Visual Studio .NET\"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\VC]
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\VC\VC_OBJECTS_PLATFORM_INFO]
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\VC\VC_OBJECTS_PLATFORM_INFO\Win32]
@="{A54AAE91-30C2-11D3-87BF-A04A4CC10000}"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.0\VC\VC_OBJECTS_PLATFORM_INFO\Win32\Directories]
"Path Dirs"="$(VCInstallDir)bin;$(VSInstallDir)Common7\Tools\bin\prerelease;$(VSInstallDir)Common7\Tools\bin;$(VSInstallDir)Common7\tools;$(VSInstallDir)Common7\ide;C:\Program Files\HTML Help Workshop\;$(FrameworkSDKDir)bin;$(FrameworkDir)$(FrameworkVersion);C:\perl\bin;C:\cygwin\bin;c:\cygwin\usr\bin;C:\bin;C:\program files\perforce;C:\cygwin\usr\local\bin\i686-pc-cygwin;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem"
"Library Dirs"="$(VCInstallDir)lib;$(VCInstallDir)atlmfc\lib;$(VCInstallDir)PlatformSDK\lib\prerelease;$(VCInstallDir)PlatformSDK\lib;$(FrameworkSDKDir)lib"
"Include Dirs"="$(VCInstallDir)include;$(VCInstallDir)atlmfc\include;$(VCInstallDir)PlatformSDK\include\prerelease;$(VCInstallDir)PlatformSDK\include;$(FrameworkSDKDir)include"
"Source Dirs"="$(VCInstallDir)atlmfc\src\mfc;$(VCInstallDir)atlmfc\src\atl;$(VCInstallDir)crt\src"
"Reference Dirs"=""
'''.split('\n')

regdata_7_1 = r'''
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1]
@=""
"Source Directories"="C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\crt\src\;C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\atlmfc\src\mfc\;C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\atlmfc\src\atl\"
"ThisVersionSolutionCLSID"="{246C57AE-40DD-4d6b-9E8D-B0F5757BB2A8}"
"ThisVersionDTECLSID"="{8CD2DD97-4EC1-4bc4-9359-89A3EEDD57A6}"
"InstallDir"="C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\IDE\"
"CLR Version"="v1.1.4322"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\InstalledProducts]
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\InstalledProducts\Smart Device Extensions]
"UseInterface"=dword:00000001
"VS7InstallDir"="C:\Program Files\Microsoft Visual Studio .NET 2003\"
"VBDeviceInstallDir"="C:\Program Files\Microsoft Visual Studio .NET 2003\VB7\"
"CSharpDeviceInstallDir"="C:\Program Files\Microsoft Visual Studio .NET 2003\VC#\"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\InstalledProducts\Visual Basic.NET]
"UseInterface"=dword:00000001
"Package"="{164B10B9-B200-11D0-8C61-00A0C91E29D5}"
"DefaultProductAttribute"="VB"
@=""
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\InstalledProducts\Visual C#]
"DefaultProductAttribute"="C#"
"UseInterface"=dword:00000001
"Package"="{FAE04EC1-301F-11D3-BF4B-00C04F79EFBC}"
@=""
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\InstalledProducts\Visual JSharp]
@=""
"Package"="{E6FDF8B0-F3D1-11D4-8576-0002A516ECE8}"
"UseInterface"=dword:00000001
"DefaultProductAttribute"="Visual JSharp"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\InstalledProducts\VisualC++]
"UseInterface"=dword:00000001
"Package"="{F1C25864-3097-11D2-A5C5-00C04F7968B4}"
"DefaultProductAttribute"="VC"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\Setup]
"Dbghelp_path"="C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\IDE\"
"dw_dir"="C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\IDE\"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\Setup\CSDPROJ]
"ProductDir"="C:\Program Files\Microsoft Visual Studio .NET 2003\VC#\"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\Setup\JSHPROJ]
"ProductDir"="C:\Program Files\Microsoft Visual Studio .NET 2003\VJ#\"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\Setup\Servicing]
"CurrentSULevel"=dword:00000000
"CurrentSPLevel"=dword:00000000
"Server Path"=""
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\Setup\Servicing\Package]
"FxSDK"=""
"VB"=""
"VC"=""
"VCS"=""
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\Setup\Servicing\SKU]
"Visual Studio .NET Professional 2003 - English"="{20610409-CA18-41A6-9E21-A93AE82EE7C5}"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\Setup\VB]
"ProductDir"="C:\Program Files\Microsoft Visual Studio .NET 2003\Vb7\"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\Setup\VBDPROJ]
"ProductDir"="C:\Program Files\Microsoft Visual Studio .NET 2003\Vb7\"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\Setup\VC]
"ProductDir"="C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\Setup\VC#]
"ProductDir"="C:\Program Files\Microsoft Visual Studio .NET 2003\VC#\"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\Setup\Visual Studio .NET Professional 2003 - English]
"InstallSuccess"=dword:00000001
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\Setup\VS]
"EnvironmentDirectory"="C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\IDE\"
"EnvironmentPath"="C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\IDE\devenv.exe"
"VS7EnvironmentLocation"="C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\IDE\devenv.exe"
"MSMDir"="C:\Program Files\Common Files\Merge Modules\"
"VS7CommonBinDir"="C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\Tools\"
"VS7CommonDir"="C:\Program Files\Microsoft Visual Studio .NET 2003\Common7\"
"ProductDir"="C:\Program Files\Microsoft Visual Studio .NET 2003\"
"VSUpdateDir"="C:\Program Files\Microsoft Visual Studio .NET 2003\Setup\VSUpdate\"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\Setup\VS\BuildNumber]
"1033"="7.1.3088"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\Setup\VS\Pro]
"ProductDir"="C:\Program Files\Microsoft Visual Studio .NET 2003\"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\VC]
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\VC\VC_OBJECTS_PLATFORM_INFO]
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\VC\VC_OBJECTS_PLATFORM_INFO\Win32]
@="{759354D0-6B42-4705-AFFB-56E34D2BC3D4}"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\VC\VC_OBJECTS_PLATFORM_INFO\Win32\Directories]
"Path Dirs"="$(VCInstallDir)bin;$(VSInstallDir)Common7\Tools\bin\prerelease;$(VSInstallDir)Common7\Tools\bin;$(VSInstallDir)Common7\tools;$(VSInstallDir)Common7\ide;C:\Program Files\HTML Help Workshop\;$(FrameworkSDKDir)bin;$(FrameworkDir)$(FrameworkVersion);C:\Perl\bin\;c:\bin;c:\cygwin\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;C:\Program Files\Common Files\Avid;C:\Program Files\backburner 2\;C:\Program Files\cvsnt;C:\Program Files\Subversion\bin;C:\Program Files\Common Files\Adobe\AGL;C:\Program Files\HTMLDoc"
"Library Dirs"="$(VCInstallDir)lib;$(VCInstallDir)atlmfc\lib;$(VCInstallDir)PlatformSDK\lib\prerelease;$(VCInstallDir)PlatformSDK\lib;$(FrameworkSDKDir)lib"
"Include Dirs"="$(VCInstallDir)include;$(VCInstallDir)atlmfc\include;$(VCInstallDir)PlatformSDK\include\prerelease;$(VCInstallDir)PlatformSDK\include;$(FrameworkSDKDir)include"
"Source Dirs"="$(VCInstallDir)atlmfc\src\mfc;$(VCInstallDir)atlmfc\src\atl;$(VCInstallDir)crt\src"
"Reference Dirs"="$(FrameWorkDir)$(FrameWorkVersion)"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\7.1\VC\VC_OBJECTS_PLATFORM_INFO\Win32\ToolDefaultExtensionLists]
"VCCLCompilerTool"="*.cpp;*.cxx;*.cc;*.c"
"VCLinkerTool"="*.obj;*.res;*.lib;*.rsc"
"VCLibrarianTool"="*.obj;*.res;*.lib;*.rsc"
"VCMIDLTool"="*.idl;*.odl"
"VCCustomBuildTool"="*.bat"
"VCResourceCompilerTool"="*.rc"
"VCPreBuildEventTool"="*.bat"
"VCPreLinkEventTool"="*.bat"
"VCPostBuildEventTool"="*.bat"
"VCBscMakeTool"="*.sbr"
"VCNMakeTool"=""
"VCWebServiceProxyGeneratorTool"="*.discomap"
"VCWebDeploymentTool"=""
"VCALinkTool"="*.resources"
"VCManagedResourceCompilerTool"="*.resx"
"VCXMLDataGeneratorTool"="*.xsd"
"VCManagedWrapperGeneratorTool"=""
"VCAuxiliaryManagedWrapperGeneratorTool"=""
"VCPrimaryInteropTool"=""
'''.split('\n')

regdata_8exp = r'''
[HKEY_LOCAL_MACHINE\Software\Microsoft\VCExpress\8.0]
"CLR Version"="v2.0.50727"
"ApplicationID"="VCExpress"
"SecurityAppID"="{741726F6-1EAE-4680-86A6-6085E8872CF8}"
"InstallDir"="C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\"
"EnablePreloadCLR"=dword:00000001
"RestoreAppPath"=dword:00000001
[HKEY_LOCAL_MACHINE\Software\Microsoft\VCExpress\8.0\InstalledProducts]
[HKEY_LOCAL_MACHINE\Software\Microsoft\VCExpress\8.0\InstalledProducts\Microsoft Visual C++]
"UseInterface"=dword:00000001
"Package"="{F1C25864-3097-11D2-A5C5-00C04F7968B4}"
"DefaultProductAttribute"="VC"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VCExpress\8.0\Setup]
[HKEY_LOCAL_MACHINE\Software\Microsoft\VCExpress\8.0\Setup\VC]
"ProductDir"="C:\Program Files\Microsoft Visual Studio 8\VC\"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VCExpress\8.0\Setup\VS]
"ProductDir"="C:\Program Files\Microsoft Visual Studio 8\"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VCExpress\8.0\VC]
[HKEY_LOCAL_MACHINE\Software\Microsoft\VCExpress\8.0\VC\VC_OBJECTS_PLATFORM_INFO]
[HKEY_LOCAL_MACHINE\Software\Microsoft\VCExpress\8.0\VC\VC_OBJECTS_PLATFORM_INFO\Win32]
@="{72f11281-2429-11d7-8bf6-00b0d03daa06}"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VCExpress\8.0\VC\VC_OBJECTS_PLATFORM_INFO\Win32\ToolDefaultExtensionLists]
"VCCLCompilerTool"="*.cpp;*.cxx;*.cc;*.c"
"VCLinkerTool"="*.obj;*.res;*.lib;*.rsc;*.licenses"
"VCLibrarianTool"="*.obj;*.res;*.lib;*.rsc"
"VCMIDLTool"="*.idl;*.odl"
"VCCustomBuildTool"="*.bat"
"VCResourceCompilerTool"="*.rc"
"VCPreBuildEventTool"="*.bat"
"VCPreLinkEventTool"="*.bat"
"VCPostBuildEventTool"="*.bat"
"VCBscMakeTool"="*.sbr"
"VCFxCopTool"="*.dll;*.exe"
"VCNMakeTool"=""
"VCWebServiceProxyGeneratorTool"="*.discomap"
"VCWebDeploymentTool"=""
"VCALinkTool"="*.resources"
"VCManagedResourceCompilerTool"="*.resx"
"VCXMLDataGeneratorTool"="*.xsd"
"VCManifestTool"="*.manifest"
"VCXDCMakeTool"="*.xdc"
'''.split('\n')

regdata_80 = r'''
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\8.0]
"CLR Version"="v2.0.50727"
"ApplicationID"="VisualStudio"
"ThisVersionDTECLSID"="{BA018599-1DB3-44f9-83B4-461454C84BF8}"
"ThisVersionSolutionCLSID"="{1B2EEDD6-C203-4d04-BD59-78906E3E8AAB}"
"SecurityAppID"="{DF99D4F5-9F04-4CEF-9D39-095821B49C77}"
"InstallDir"="C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\"
"EnablePreloadCLR"=dword:00000001
"RestoreAppPath"=dword:00000001
"Source Directories"="C:\Program Files\Microsoft Visual Studio 8\VC\crt\src\;C:\Program Files\Microsoft Visual Studio 8\VC\atlmfc\src\mfc\;C:\Program Files\Microsoft Visual Studio 8\VC\atlmfc\src\atl\;C:\Program Files\Microsoft Visual Studio 8\VC\atlmfc\include\"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\8.0\InstalledProducts]
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\8.0\InstalledProducts\Microsoft Visual C++]
"UseInterface"=dword:00000001
"Package"="{F1C25864-3097-11D2-A5C5-00C04F7968B4}"
"DefaultProductAttribute"="VC"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\8.0\Setup]
"Dbghelp_path"="C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\8.0\Setup\EF]
"ProductDir"="C:\Program Files\Microsoft Visual Studio 8\EnterpriseFrameworks\"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\8.0\Setup\Microsoft Visual Studio 2005 Professional Edition - ENU]
"SrcPath"="d:\vs\"
"InstallSuccess"=dword:00000001
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\8.0\Setup\VC]
"ProductDir"="C:\Program Files\Microsoft Visual Studio 8\VC\"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\8.0\Setup\VS]
"ProductDir"="C:\Program Files\Microsoft Visual Studio 8\"
"VS7EnvironmentLocation"="C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\devenv.exe"
"EnvironmentPath"="C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\devenv.exe"
"EnvironmentDirectory"="C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\"
"VS7CommonDir"="C:\Program Files\Microsoft Visual Studio 8\Common7\"
"VS7CommonBinDir"="C:\Program Files\Microsoft Visual Studio 8\Common7\Tools\"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\8.0\Setup\VS\BuildNumber]
"1033"="8.0.50727.42"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\8.0\Setup\VS\Pro]
"ProductDir"="C:\Program Files\Microsoft Visual Studio 8\"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\8.0\VC]
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\8.0\VC\VC_OBJECTS_PLATFORM_INFO]
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\8.0\VC\VC_OBJECTS_PLATFORM_INFO\Win32]
@="{72f11281-2429-11d7-8bf6-00b0d03daa06}"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\8.0\VC\VC_OBJECTS_PLATFORM_INFO\Win32\ToolDefaultExtensionLists]
"VCCLCompilerTool"="*.cpp;*.cxx;*.cc;*.c"
"VCLinkerTool"="*.obj;*.res;*.lib;*.rsc;*.licenses"
"VCLibrarianTool"="*.obj;*.res;*.lib;*.rsc"
"VCMIDLTool"="*.idl;*.odl"
"VCCustomBuildTool"="*.bat"
"VCResourceCompilerTool"="*.rc"
"VCPreBuildEventTool"="*.bat"
"VCPreLinkEventTool"="*.bat"
"VCPostBuildEventTool"="*.bat"
"VCBscMakeTool"="*.sbr"
"VCFxCopTool"="*.dll;*.exe"
"VCNMakeTool"=""
"VCWebServiceProxyGeneratorTool"="*.discomap"
"VCWebDeploymentTool"=""
"VCALinkTool"="*.resources"
"VCManagedResourceCompilerTool"="*.resx"
"VCXMLDataGeneratorTool"="*.xsd"
"VCManifestTool"="*.manifest"
"VCXDCMakeTool"="*.xdc"
'''.split('\n')

regdata_140 = r'''
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\14.0\Setup\VS]
"MSMDir"="C:\\Program Files (x86)\\Common Files\\Merge Modules\\"
"ProductDir"="C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\"
"VS7EnvironmentLocation"="C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\Common7\\IDE\\devenv.exe"
"EnvironmentPath"="C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\Common7\\IDE\\devenv.exe"
"EnvironmentDirectory"="C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\Common7\\IDE\\"
"VS7CommonDir"="C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\Common7\\"
"VS7CommonBinDir"=""
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\14.0\Setup\VS\BuildNumber]
"1033"="14.0"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\14.0\Setup\VS\Community]
"ProductDir"="C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\14.0\Setup\VS\JSLS_MSI]
"Version"="14.0.25527"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\14.0\Setup\VS\JSPS_MSI]
"Version"="14.0.25527"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\14.0\Setup\VS\Pro]
"ProductDir"="C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\14.0\Setup\VS\professional]
"IsInstallInProgress"="0"
"CurrentOperation"="install"
"SetupFeedUri"="http://go.microsoft.com/fwlink/?LinkID=659004&clcid=0x409"
"SetupFeedLocalCache"="C:\\ProgramData\\Microsoft\\VisualStudioSecondaryInstaller\\14.0\\LastUsedFeed\\{68432bbb-c9a5-4a7b-bab3-ae5a49b28303}\\Feed.xml"
"InstallResult"="0"
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\14.0\Setup\VS\SecondaryInstaller]
[HKEY_LOCAL_MACHINE\Software\Microsoft\VisualStudio\14.0\Setup\VS\SecondaryInstaller\AppInsightsTools]
"Version"="7.0.20620.1"
'''.split('\n')

regdata_cv = r'''[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion]
"ProgramFilesDir"="C:\Program Files"
"CommonFilesDir"="C:\Program Files\Common Files"
"MediaPath"="C:\WINDOWS\Media"
'''.split('\n')


regdata_none = []

class DummyEnv:
    def __init__(self, dict=None):
        self.fs = SCons.Node.FS.FS()
        if dict:
            self.dict = dict
        else:
            self.dict = {}

    def Dictionary(self, key = None):
        if not key:
            return self.dict
        return self.dict[key]

    def __setitem__(self, key, value):
        self.dict[key] = value

    def __getitem__(self, key):
        return self.dict[key]

    def __contains__(self, key):
        return key in self.dict

    def get(self, name, value=None):
        if name in self.dict:
            return self.dict[name]
        else:
            return value

    def Dir(self, name):
        return self.fs.Dir(name)


class RegKey:
    """key class for storing an 'open' registry key"""
    def __init__(self,key):
        self.key = key

# Warning: this is NOT case-insensitive, unlike the Windows registry.
# So e.g. HKLM\Software is NOT the same key as HKLM\SOFTWARE.
class RegNode:
    """node in the dummy registry"""
    def __init__(self,name):
        self.valdict = {}
        self.keydict = {}
        self.keyarray = []
        self.valarray = []
        self.name = name

    def value(self,val):
        if val in self.valdict:
            return (self.valdict[val],1)
        else:
            raise SCons.Util.RegError

    def addValue(self,name,val):
        self.valdict[name] = val
        self.valarray.append(name)

    def valindex(self,index):
        rv = None
        try:
            rv = (self.valarray[index],self.valdict[self.valarray[index]],1)
        except KeyError:
            raise SCons.Util.RegError
        return rv

    def key(self,key,sep = '\\'):
        if key.find(sep) != -1:
            keyname, subkeys = key.split(sep,1)
        else:
            keyname = key
            subkeys = ""
        try:
            # recurse, and return the lowest level key node
            if subkeys:
                return self.keydict[keyname].key(subkeys)
            else:
                return self.keydict[keyname]
        except KeyError:
            raise SCons.Util.RegError

    def addKey(self,name,sep = '\\'):
        if name.find(sep) != -1:
            keyname, subkeys = name.split(sep, 1)
        else:
            keyname = name
            subkeys = ""

        if keyname not in self.keydict:
            self.keydict[keyname] = RegNode(keyname)
            self.keyarray.append(keyname)

        # recurse, and return the lowest level key node
        if subkeys:
            return self.keydict[keyname].addKey(subkeys)
        else:
            return self.keydict[keyname]

    def keyindex(self,index):
        return self.keydict[self.keyarray[index]]

    def __str__(self):
        return self._doStr()

    def _doStr(self, indent = ''):
        rv = ""
        for value in self.valarray:
            rv = rv + '%s"%s" = "%s"\n' % (indent, value, self.valdict[value])
        for key in self.keyarray:
            rv = rv + "%s%s: {\n"%(indent, key)
            rv = rv + self.keydict[key]._doStr(indent + '  ')
            rv = rv + indent + '}\n'
        return rv

class DummyRegistry:
    """registry class for storing fake registry attributes"""
    def __init__(self,data):
        """parse input data into the fake registry"""
        self.root = RegNode('REGISTRY')
        self.root.addKey('HKEY_LOCAL_MACHINE')
        self.root.addKey('HKEY_CURRENT_USER')
        self.root.addKey('HKEY_USERS')
        self.root.addKey('HKEY_CLASSES_ROOT')

        self.parse(data)

    def parse(self, data):
        parents = [None, None]
        parents[0] = self.root
        keymatch = re.compile(r'^\[(.*)\]$')
        valmatch = re.compile(r'^(?:"(.*)"|[@])="(.*)"$')
        for line in data:
            m1 = keymatch.match(line)
            if m1:
                # add a key, set it to current parent.
                # Also create subkey for Wow6432Node of HKLM\Software;
                # that's where it looks on a 64-bit machine (tests will fail w/o this)
                parents[0] = self.root.addKey(m1.group(1))
                if 'HKEY_LOCAL_MACHINE\\Software' in m1.group(1):
                    p1 = m1.group(1).replace('HKEY_LOCAL_MACHINE\\Software', 'HKEY_LOCAL_MACHINE\\Software\\Wow6432Node')
                    parents[1] = self.root.addKey(p1)
                else:
                    parents[1] = None
            else:
                m2 = valmatch.match(line)
                if m2:
                    for p in parents:
                        if p:
                            p.addValue(m2.group(1),m2.group(2))

    def OpenKeyEx(self,root,key):
        if root == SCons.Util.HKEY_CLASSES_ROOT:
            mykey = 'HKEY_CLASSES_ROOT\\' + key
        if root == SCons.Util.HKEY_USERS:
            mykey = 'HKEY_USERS\\' + key
        if root == SCons.Util.HKEY_CURRENT_USER:
            mykey = 'HKEY_CURRENT_USER\\' + key
        if root == SCons.Util.HKEY_LOCAL_MACHINE:
            mykey = 'HKEY_LOCAL_MACHINE\\' + key
        debug("Open Key:%s"%mykey)
        return self.root.key(mykey)

def DummyOpenKeyEx(root, key):
    return registry.OpenKeyEx(root,key)

def DummyEnumKey(key, index):
    rv = None
    try:
        rv = key.keyarray[index]
    except IndexError:
        raise SCons.Util.RegError
    # DEBUG
    # print "Enum Key",key.name,"[",index,"] =>",rv
    return rv

def DummyEnumValue(key, index):
    rv = key.valindex(index)
    # DEBUG
    # print "Enum Value",key.name,"[",index,"] =>",rv
    return rv

def DummyQueryValue(key, value):
    rv = key.value(value)
    # DEBUG
    # print "Query Value",key.name+"\\"+value,"=>",rv
    return rv

def DummyExists(path):
    return 1

def DummyVsWhere(msvc_version, env):
    # not testing versions with vswhere, so return none
    return None

class msvsTestCase(unittest.TestCase):
    """This test case is run several times with different defaults.
    See its subclasses below."""
    def setUp(self):
        debug("THIS TYPE :%s"%self)
        global registry
        registry = self.registry
        from SCons.Tool.MSCommon.vs import reset_installed_visual_studios
        reset_installed_visual_studios()

        self.test = TestCmd.TestCmd(workdir='')
        # FS doesn't like the cwd to be something other than its root.
        os.chdir(self.test.workpath(""))
        self.fs = SCons.Node.FS.FS()

    def test_get_default_version(self):
        """Test retrieval of the default visual studio version"""

        debug("Testing for default version %s"%self.default_version)
        env = DummyEnv()
        v1 = get_default_version(env)
        if v1:
            assert env['MSVS_VERSION'] == self.default_version, \
                   ("env['MSVS_VERSION'] != self.default_version",
                    env['MSVS_VERSION'],self.default_version)
            assert env['MSVS']['VERSION'] == self.default_version, \
                   ("env['MSVS']['VERSION'] != self.default_version",
                    env['MSVS']['VERSION'], self.default_version)
            assert v1 == self.default_version, (self.default_version, v1)

        env = DummyEnv({'MSVS_VERSION':'7.0'})
        v2 = get_default_version(env)
        assert env['MSVS_VERSION'] == '7.0', env['MSVS_VERSION']
        assert env['MSVS']['VERSION'] == '7.0', env['MSVS']['VERSION']
        assert v2 == '7.0', v2

        env = DummyEnv()
        v3 = get_default_version(env)
        if v3 == '7.1':
            override = '7.0'
        else:
            override = '7.1'
        env['MSVS_VERSION'] = override
        v3 = get_default_version(env)
        assert env['MSVS_VERSION'] == override, env['MSVS_VERSION']
        assert env['MSVS']['VERSION'] == override, env['MSVS']['VERSION']
        assert v3 == override, v3

    def _TODO_test_merge_default_version(self):
        """Test the merge_default_version() function"""
        pass

    def test_query_versions(self):
        """Test retrieval of the list of visual studio versions"""
        v1 = query_versions()
        assert not v1 or str(v1[0]) == self.highest_version, \
               (v1, self.highest_version)
        assert len(v1) == self.number_of_versions, v1
        
    def test_config_generation(self):
        """Test _DSPGenerator.__init__(...)"""
        if not self.highest_version :
            return
        
        # Initialize 'static' variables
        version_num, suite = msvs_parse_version(self.highest_version)
        if version_num >= 10.0:
            function_test = _GenerateV10DSP
            suffix = '.vcxproj'
        elif version_num >= 7.0:
            function_test = _GenerateV7DSP
            suffix = '.dsp'
        else:
            function_test = _GenerateV6DSP
            suffix = '.dsp'

        # Avoid any race conditions between the test cases when we test
        # actually writing the files.
        dspfile = 'test%s%s' % (hash(self), suffix)
            
        str_function_test = str(function_test.__init__)
        source = 'test.cpp'
        
        # Create the cmdargs test list
        list_variant = ['Debug|Win32','Release|Win32',
                        'Debug|x64', 'Release|x64']
        list_cmdargs = ['debug=True target_arch=32', 
                        'debug=False target_arch=32',
                        'debug=True target_arch=x64', 
                        'debug=False target_arch=x64']
        list_cppdefines = [['_A', '_B', 'C'], ['_B', '_C_'], ['D'], []]
        list_cpppaths = [[r'C:\test1'], [r'C:\test1;C:\test2'],
                         [self.fs.Dir('subdir')], []]
        list_cppflags = [['/std:c++17', '/Zc:__cplusplus'],
                         ['/std:c++14', '/Zc:__cplusplus'],
                         ['/std:c++11', '/Zc:__cplusplus'], []]

        def TestParamsFromList(test_variant, test_list):
            """
            Generates test data based on the parameters passed in.

            Returns tuple list:
                1. Parameter.
                2. Dictionary of expected result per variant.
            """
            def normalizeParam(param):
                """
                Converts the raw data based into the AddConfig function of
                msvs.py to the expected result.

                Expects the following behavior:
                    1. A value of None will be converted to an empty list.
                    2. A File or Directory object will be converted to an
                       absolute path (because those objects can't be pickled).
                    3. Otherwise, the parameter will be used.
                """
                if param is None:
                    return []
                elif isinstance(param, list):
                    return [normalizeParam(p) for p in param]
                elif hasattr(param, 'abspath'):
                    return param.abspath
                else:
                    return param

            return [
                (None, dict.fromkeys(test_variant, '')),
                ('', dict.fromkeys(test_variant, '')),
                (test_list[0], dict.fromkeys(test_variant, normalizeParam(test_list[0]))),
                (test_list, dict(list(zip(test_variant, [normalizeParam(x) for x in test_list]))))
            ]

        tests_cmdargs = TestParamsFromList(list_variant, list_cmdargs)
        tests_cppdefines = TestParamsFromList(list_variant, list_cppdefines)
        tests_cpppaths = TestParamsFromList(list_variant, list_cpppaths)
        tests_cppflags = TestParamsFromList(list_variant, list_cppflags)

        # Run the test for each test case
        for param_cmdargs, expected_cmdargs in tests_cmdargs:
            for param_cppdefines, expected_cppdefines in tests_cppdefines:
                for param_cpppaths, expected_cpppaths in tests_cpppaths:
                    for param_cppflags, expected_cppflags in tests_cppflags:
                        # DEBUG:
                        # print(
                        #     'Testing %s. with :\n  variant = %s \n  cmdargs = "%s" \n  cppdefines = "%s" \n  cpppaths = "%s" \n  cppflags = "%s"'
                        #     % (
                        #         str_function_test,
                        #         list_variant,
                        #         param_cmdargs,
                        #         param_cppdefines,
                        #         param_cpppaths,
                        #         param_cppflags,
                        #     )
                        # )
                        param_configs = []
                        expected_configs = {}
                        for platform in ['Win32', 'x64']:
                            for variant in ['Debug', 'Release']:
                                variant_platform = '%s|%s' % (variant, platform)
                                runfile = '%s\\%s\\test.exe' % (platform, variant)
                                buildtarget = '%s\\%s\\test.exe' % (platform, variant)
                                outdir = '%s\\%s' % (platform, variant)

                                # Create parameter list for this variant_platform
                                param_configs.append([variant_platform, runfile, buildtarget, outdir])

                                # Create expected dictionary result for this variant_platform
                                expected_configs[variant_platform] = {
                                    'variant': variant,
                                    'platform': platform,
                                    'runfile': runfile,
                                    'buildtarget': buildtarget,
                                    'outdir': outdir,
                                    'cmdargs': expected_cmdargs[variant_platform],
                                    'cppdefines': expected_cppdefines[variant_platform],
                                    'cpppaths': expected_cpppaths[variant_platform],
                                    'cppflags': expected_cppflags[variant_platform],
                                }

            # Create parameter environment with final parameter dictionary
            param_dict = dict(list(zip(('variant', 'runfile', 'buildtarget', 'outdir'),
                                  [list(l) for l in zip(*param_configs)])))
            param_dict['cmdargs'] = param_cmdargs
            param_dict['cppdefines'] = param_cppdefines
            param_dict['cpppaths'] = param_cpppaths
            param_dict['cppflags'] = param_cppflags

            # Hack to be able to run the test with a 'DummyEnv'
            class _DummyEnv(DummyEnv):
                def subst(self, string, *args, **kwargs):
                    return string
            
            env = _DummyEnv(param_dict)
            env['MSVSSCONSCRIPT'] = ''
            env['MSVS_VERSION'] = self.highest_version
            env['MSVSBUILDTARGET'] = 'target'
           
            # Call function to test
            genDSP = function_test(dspfile, source, env)
        
            # Check expected result
            self.assertListEqual(list(genDSP.configs.keys()), list(expected_configs.keys()))
            for key, v in genDSP.configs.items():
                self.assertDictEqual(v.__dict__, expected_configs[key])

            genDSP.Build()

            # Delete the resulting file so we don't leave anything behind.
            for file in [dspfile, dspfile + '.filters']:
                path = os.path.realpath(file)
                try:
                    os.remove(path)
                except OSError:
                    pass

class msvs6aTestCase(msvsTestCase):
    """Test MSVS 6 Registry"""
    registry = DummyRegistry(regdata_6a + regdata_cv)
    default_version = '6.0'
    highest_version = '6.0'
    number_of_versions = 1
    install_locs = {
        '6.0' : {'VSINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio\\VC98', 'VCINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio\\VC98\\Bin'},
        '7.0' : {},
        '7.1' : {},
        '8.0' : {},
        '8.0Exp' : {},
    }
    default_install_loc = install_locs['6.0']

class msvs6bTestCase(msvsTestCase):
    """Test Other MSVS 6 Registry"""
    registry = DummyRegistry(regdata_6b + regdata_cv)
    default_version = '6.0'
    highest_version = '6.0'
    number_of_versions = 1
    install_locs = {
        '6.0' : {'VSINSTALLDIR': 'C:\\VS6\\VC98', 'VCINSTALLDIR': 'C:\\VS6\\VC98\\Bin'},
        '7.0' : {},
        '7.1' : {},
        '8.0' : {},
        '8.0Exp' : {},
    }
    default_install_loc = install_locs['6.0']

class msvs6and7TestCase(msvsTestCase):
    """Test MSVS 6 & 7 Registry"""
    registry = DummyRegistry(regdata_6b + regdata_7 + regdata_cv)
    default_version = '7.0'
    highest_version = '7.0'
    number_of_versions = 2
    install_locs = {
        '6.0' : {'VSINSTALLDIR': 'C:\\VS6\\VC98',
                 'VCINSTALLDIR': 'C:\\VS6\\VC98\\Bin'},
        '7.0' : {'VSINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio .NET\\Common7',
                 'VCINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio .NET\\Common7\\Tools'},
        '7.1' : {},
        '8.0' : {},
        '8.0Exp' : {},
    }
    default_install_loc = install_locs['7.0']

class msvs7TestCase(msvsTestCase):
    """Test MSVS 7 Registry"""
    registry = DummyRegistry(regdata_7 + regdata_cv)
    default_version = '7.0'
    highest_version = '7.0'
    number_of_versions = 1
    install_locs = {
        '6.0' : {},
        '7.0' : {'VSINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio .NET\\Common7',
                 'VCINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio .NET\\Common7\\Tools'},
        '7.1' : {},
        '8.0' : {},
        '8.0Exp' : {},
    }
    default_install_loc = install_locs['7.0']

class msvs71TestCase(msvsTestCase):
    """Test MSVS 7.1 Registry"""
    registry = DummyRegistry(regdata_7_1 + regdata_cv)
    default_version = '7.1'
    highest_version = '7.1'
    number_of_versions = 1
    install_locs = {
        '6.0' : {},
        '7.0' : {},
        '7.1' : {'VSINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio .NET 2003\\Common7',
                 'VCINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio .NET 2003\\Common7\\Tools'},
        '8.0' : {},
        '8.0Exp' : {},
    }
    default_install_loc = install_locs['7.1']

class msvs8ExpTestCase(msvsTestCase): # XXX: only one still not working
    """Test MSVS 8 Express Registry"""
    registry = DummyRegistry(regdata_8exp + regdata_cv)
    default_version = '8.0Exp'
    highest_version = '8.0Exp'
    number_of_versions = 1
    install_locs = {
        '6.0' : {},
        '7.0' : {},
        '7.1' : {},
        '8.0' : {},
        '8.0Exp' : {'VSINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio 8',
                    'VCINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio 8\\VC'},
    }
    default_install_loc = install_locs['8.0Exp']

class msvs80TestCase(msvsTestCase):
    """Test MSVS 8 Registry"""
    registry = DummyRegistry(regdata_80 + regdata_cv)
    default_version = '8.0'
    highest_version = '8.0'
    number_of_versions = 1
    install_locs = {
        '6.0' : {},
        '7.0' : {},
        '7.1' : {},
        '8.0' : {'VSINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio 8',
                 'VCINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio 8\\VC'},
        '8.0Exp' : {},
    }
    default_install_loc = install_locs['8.0']

class msvs140TestCase(msvsTestCase):
    """Test MSVS 140 Registry"""
    registry = DummyRegistry(regdata_140 + regdata_cv)
    default_version = '14.0'
    highest_version = '14.0'
    number_of_versions = 2
    install_locs = {
        '14.0' : {'VSINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio 14.0',
                  'VCINSTALLDIR': 'C:\\Program Files\\Microsoft Visual Studio 14.0\\VC'},
    }
    default_install_loc = install_locs['14.0']

class msvsEmptyTestCase(msvsTestCase):
    """Test Empty Registry"""
    registry = DummyRegistry(regdata_none)
    default_version = SupportedVSList[0].version
    highest_version = None
    number_of_versions = 0
    install_locs = {
        '6.0' : {},
        '7.0' : {},
        '7.1' : {},
        '8.0' : {},
        '8.0Exp' : {},
    }
    default_install_loc = install_locs['8.0Exp']

if __name__ == "__main__":

    # only makes sense to test this on win32
    if sys.platform != 'win32':
        sys.stdout.write("NO RESULT for msvsTests.py:  '%s' is not win32\n" % sys.platform)
        sys.exit(0)

    SCons.Util.RegOpenKeyEx    = DummyOpenKeyEx
    SCons.Util.RegEnumKey      = DummyEnumKey
    SCons.Util.RegEnumValue    = DummyEnumValue
    SCons.Util.RegQueryValueEx = DummyQueryValue
    SCons.Tool.MSCommon.vc.find_vc_pdir_vswhere = DummyVsWhere

    os.path.exists = DummyExists # make sure all files exist :-)
    os.path.isfile = DummyExists # make sure all files are files :-)
    os.path.isdir  = DummyExists # make sure all dirs are dirs :-)

    exit_val = 0

    test_classes = [
        msvs6aTestCase,
        msvs6bTestCase,
        msvs6and7TestCase,
        msvs7TestCase,
        msvs71TestCase,
        msvs8ExpTestCase,
        msvs80TestCase,
        msvs140TestCase,
        msvsEmptyTestCase,
    ]

    for test_class in test_classes:
        # DEBUG
        # print("TEST: ", test_class.__doc__)
        back_osenv = copy.deepcopy(os.environ)
        try:
            # XXX: overriding the os.environ is bad, but doing it
            # correctly is too complicated for now. Those tests should
            # be fixed
            for k in ['VS71COMNTOOLS',
                      'VS80COMNTOOLS',
                      'VS90COMNTOOLS']:
                if k in os.environ:
                    del os.environ[k]

            loader = unittest.TestLoader()
            loader.testMethodPrefix = 'test_'
            suite = loader.loadTestsFromTestCase(test_class)
            if not TestUnit.cli.get_runner()().run(suite).wasSuccessful():
                exit_val = 1
        finally:
            os.env = back_osenv

    sys.exit(exit_val)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/mwcc.py

"""SCons.Tool.mwcc

Tool-specific initialization for the Metrowerks CodeWarrior compiler.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import os
import os.path

import SCons.Util

def set_vars(env):
    """Set MWCW_VERSION, MWCW_VERSIONS, and some codewarrior environment vars

    MWCW_VERSIONS is set to a list of objects representing installed versions

    MWCW_VERSION  is set to the version object that will be used for building.
                  MWCW_VERSION can be set to a string during Environment
                  construction to influence which version is chosen, otherwise
                  the latest one from MWCW_VERSIONS is used.

    Returns true if at least one version is found, false otherwise
    """
    desired = env.get('MWCW_VERSION', '')

    # return right away if the variables are already set
    if isinstance(desired, MWVersion):
        return 1
    elif desired is None:
        return 0

    versions = find_versions()
    version = None

    if desired:
        for v in versions:
            if str(v) == desired:
                version = v
    elif versions:
        version = versions[-1]

    env['MWCW_VERSIONS'] = versions
    env['MWCW_VERSION'] = version

    if version is None:
      return 0

    env.PrependENVPath('PATH', version.clpath)
    env.PrependENVPath('PATH', version.dllpath)
    ENV = env['ENV']
    ENV['CWFolder'] = version.path
    ENV['LM_LICENSE_FILE'] = version.license
    plus = lambda x: '+%s' % x
    ENV['MWCIncludes'] = os.pathsep.join(map(plus, version.includes))
    ENV['MWLibraries'] = os.pathsep.join(map(plus, version.libs))
    return 1


def find_versions():
    """Return a list of MWVersion objects representing installed versions"""
    versions = []

    ### This function finds CodeWarrior by reading from the registry on
    ### Windows. Some other method needs to be implemented for other
    ### platforms, maybe something that calls env.WhereIs('mwcc')

    if SCons.Util.can_read_reg:
        try:
            HLM = SCons.Util.HKEY_LOCAL_MACHINE
            product = 'SOFTWARE\\Metrowerks\\CodeWarrior\\Product Versions'
            product_key = SCons.Util.RegOpenKeyEx(HLM, product)

            i = 0
            while True:
                name = product + '\\' + SCons.Util.RegEnumKey(product_key, i)
                name_key = SCons.Util.RegOpenKeyEx(HLM, name)

                try:
                    version = SCons.Util.RegQueryValueEx(name_key, 'VERSION')
                    path = SCons.Util.RegQueryValueEx(name_key, 'PATH')
                    mwv = MWVersion(version[0], path[0], 'Win32-X86')
                    versions.append(mwv)
                except SCons.Util.RegError:
                    pass

                i = i + 1

        except SCons.Util.RegError:
            pass

    return versions


class MWVersion:
    def __init__(self, version, path, platform):
        self.version = version
        self.path = path
        self.platform = platform
        self.clpath = os.path.join(path, 'Other Metrowerks Tools',
                                   'Command Line Tools')
        self.dllpath = os.path.join(path, 'Bin')

        # The Metrowerks tools don't store any configuration data so they
        # are totally dumb when it comes to locating standard headers,
        # libraries, and other files, expecting all the information
        # to be handed to them in environment variables. The members set
        # below control what information scons injects into the environment

        ### The paths below give a normal build environment in CodeWarrior for
        ### Windows, other versions of CodeWarrior might need different paths.

        msl = os.path.join(path, 'MSL')
        support = os.path.join(path, '%s Support' % platform)

        self.license = os.path.join(path, 'license.dat')
        self.includes = [msl, support]
        self.libs = [msl, support]

    def __str__(self):
        return self.version


CSuffixes = ['.c', '.C']
CXXSuffixes = ['.cc', '.cpp', '.cxx', '.c++', '.C++']


def generate(env):
    """Add Builders and construction variables for the mwcc to an Environment."""
    import SCons.Defaults
    import SCons.Tool

    set_vars(env)

    static_obj, shared_obj = SCons.Tool.createObjBuilders(env)

    for suffix in CSuffixes:
        static_obj.add_action(suffix, SCons.Defaults.CAction)
        shared_obj.add_action(suffix, SCons.Defaults.ShCAction)

    for suffix in CXXSuffixes:
        static_obj.add_action(suffix, SCons.Defaults.CXXAction)
        shared_obj.add_action(suffix, SCons.Defaults.ShCXXAction)

    env['CCCOMFLAGS'] = '$CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS -nolink -o $TARGET $SOURCES'

    env['CC']         = 'mwcc'
    env['CCCOM']      = '$CC $CFLAGS $CCFLAGS $CCCOMFLAGS'

    env['CXX']        = 'mwcc'
    env['CXXCOM']     = '$CXX $CXXFLAGS $CCCOMFLAGS'

    env['SHCC']       = '$CC'
    env['SHCCFLAGS']  = '$CCFLAGS'
    env['SHCFLAGS']   = '$CFLAGS'
    env['SHCCCOM']    = '$SHCC $SHCFLAGS $SHCCFLAGS $CCCOMFLAGS'

    env['SHCXX']       = '$CXX'
    env['SHCXXFLAGS']  = '$CXXFLAGS'
    env['SHCXXCOM']    = '$SHCXX $SHCXXFLAGS $CCCOMFLAGS'

    env['CFILESUFFIX'] = '.c'
    env['CXXFILESUFFIX'] = '.cpp'
    env['CPPDEFPREFIX']  = '-D'
    env['CPPDEFSUFFIX']  = ''
    env['INCPREFIX']  = '-I'
    env['INCSUFFIX']  = ''

    #env['PCH'] = ?
    #env['PCHSTOP'] = ?


def exists(env):
    return set_vars(env)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/mwld.py

"""SCons.Tool.mwld

Tool-specific initialization for the Metrowerks CodeWarrior linker.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import SCons.Tool


def generate(env):
    """Add Builders and construction variables for lib to an Environment."""
    SCons.Tool.createStaticLibBuilder(env)
    SCons.Tool.createSharedLibBuilder(env)
    SCons.Tool.createProgBuilder(env)

    env['AR'] = 'mwld'
    env['ARCOM'] = '$AR $ARFLAGS -library -o $TARGET $SOURCES'

    env['LIBDIRPREFIX'] = '-L'
    env['LIBDIRSUFFIX'] = ''
    env['LIBLINKPREFIX'] = '-l'
    env['LIBLINKSUFFIX'] = '.lib'

    env['LINK'] = 'mwld'
    env['LINKCOM'] = '$LINK $LINKFLAGS -o $TARGET $SOURCES $_LIBDIRFLAGS $_LIBFLAGS'

    env['SHLINK'] = '$LINK'
    env['SHLINKFLAGS'] = '$LINKFLAGS'
    env['SHLINKCOM']   = shlib_action
    env['SHLIBEMITTER']= shlib_emitter
    env['LDMODULEEMITTER']= shlib_emitter


def exists(env):
    import SCons.Tool.mwcc
    return SCons.Tool.mwcc.set_vars(env)


def shlib_generator(target, source, env, for_signature):
    cmd = ['$SHLINK', '$SHLINKFLAGS', '-shared']

    no_import_lib = env.get('no_import_lib', 0)
    if no_import_lib: cmd.extend('-noimplib')

    dll = env.FindIxes(target, 'SHLIBPREFIX', 'SHLIBSUFFIX')
    if dll: cmd.extend(['-o', dll])

    implib = env.FindIxes(target, 'LIBPREFIX', 'LIBSUFFIX')
    if implib: cmd.extend(['-implib', implib.get_string(for_signature)])

    cmd.extend(['$SOURCES', '$_LIBDIRFLAGS', '$_LIBFLAGS'])

    return [cmd]


def shlib_emitter(target, source, env):
    dll = env.FindIxes(target, 'SHLIBPREFIX', 'SHLIBSUFFIX')
    no_import_lib = env.get('no_import_lib', 0)

    if not dll:
        raise SCons.Errors.UserError("A shared library should have exactly one target with the suffix: %s" % env.subst("$SHLIBSUFFIX"))

    if not no_import_lib and \
       not env.FindIxes(target, 'LIBPREFIX', 'LIBSUFFIX'):

        # Append an import library to the list of targets.
        target.append(env.ReplaceIxes(dll,
                                      'SHLIBPREFIX', 'SHLIBSUFFIX',
                                      'LIBPREFIX', 'LIBSUFFIX'))

    return target, source


shlib_action = SCons.Action.Action(shlib_generator, generator=1)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/nasm.py

"""SCons.Tool.nasm

Tool-specific initialization for nasm, the famous Netwide Assembler.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import SCons.Defaults
import SCons.Tool
import SCons.Util

ASSuffixes = ['.s', '.asm', '.ASM']
ASPPSuffixes = ['.spp', '.SPP', '.sx']
if SCons.Util.case_sensitive_suffixes('.s', '.S'):
    ASPPSuffixes.extend(['.S'])
else:
    ASSuffixes.extend(['.S'])

def generate(env):
    """Add Builders and construction variables for nasm to an Environment."""
    static_obj, shared_obj = SCons.Tool.createObjBuilders(env)

    for suffix in ASSuffixes:
        static_obj.add_action(suffix, SCons.Defaults.ASAction)
        static_obj.add_emitter(suffix, SCons.Defaults.StaticObjectEmitter)

    for suffix in ASPPSuffixes:
        static_obj.add_action(suffix, SCons.Defaults.ASPPAction)
        static_obj.add_emitter(suffix, SCons.Defaults.StaticObjectEmitter)

    env['AS']        = 'nasm'
    env['ASFLAGS']   = SCons.Util.CLVar('')
    env['ASPPFLAGS'] = '$ASFLAGS'
    env['ASCOM']     = '$AS $ASFLAGS -o $TARGET $SOURCES'
    env['ASPPCOM']   = '$CC $ASPPFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS -c -o $TARGET $SOURCES'

def exists(env):
    return env.Detect('nasm')

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/pdf.py

"""SCons.Tool.pdf

Common PDF Builder definition for various other Tool modules that use it.
Add an explicit action to run epstopdf to convert .eps files to .pdf

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import SCons.Builder
import SCons.Tool

PDFBuilder = None

EpsPdfAction = SCons.Action.Action('$EPSTOPDFCOM', '$EPSTOPDFCOMSTR')

def generate(env):
    try:
        env['BUILDERS']['PDF']
    except KeyError:
        global PDFBuilder
        if PDFBuilder is None:
            PDFBuilder = SCons.Builder.Builder(action = {},
                                               source_scanner = SCons.Tool.PDFLaTeXScanner,
                                               prefix = '$PDFPREFIX',
                                               suffix = '$PDFSUFFIX',
                                               emitter = {},
                                               source_ext_match = None,
                                               single_source=True)
        env['BUILDERS']['PDF'] = PDFBuilder

    env['PDFPREFIX'] = ''
    env['PDFSUFFIX'] = '.pdf'

# put the epstopdf builder in this routine so we can add it after 
# the pdftex builder so that one is the default for no source suffix
def generate2(env):
    bld = env['BUILDERS']['PDF']
    #bld.add_action('.ps',  EpsPdfAction) # this is covered by direct Ghostcript action in gs.py
    bld.add_action('.eps', EpsPdfAction)

    env['EPSTOPDF']      = 'epstopdf'
    env['EPSTOPDFFLAGS'] = SCons.Util.CLVar('')
    env['EPSTOPDFCOM']   = '$EPSTOPDF $EPSTOPDFFLAGS ${SOURCE} --outfile=${TARGET}'

def exists(env):
    # This only puts a skeleton Builder in place, so if someone
    # references this Tool directly, it's always "available."
    return 1

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/pdflatex.py

"""SCons.Tool.pdflatex

Tool-specific initialization for pdflatex.
Generates .pdf files from .latex or .ltx files

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import SCons.Action
import SCons.Util
import SCons.Tool.pdf
import SCons.Tool.tex

PDFLaTeXAction = None

def PDFLaTeXAuxFunction(target = None, source= None, env=None):
    result = SCons.Tool.tex.InternalLaTeXAuxAction( PDFLaTeXAction, target, source, env )
    if result != 0:
        SCons.Tool.tex.check_file_error_message(env['PDFLATEX'])
    return result

PDFLaTeXAuxAction = None

def generate(env):
    """Add Builders and construction variables for pdflatex to an Environment."""
    global PDFLaTeXAction
    if PDFLaTeXAction is None:
        PDFLaTeXAction = SCons.Action.Action('$PDFLATEXCOM', '$PDFLATEXCOMSTR')

    global PDFLaTeXAuxAction
    if PDFLaTeXAuxAction is None:
        PDFLaTeXAuxAction = SCons.Action.Action(PDFLaTeXAuxFunction,
                              strfunction=SCons.Tool.tex.TeXLaTeXStrFunction)

    env.AppendUnique(LATEXSUFFIXES=SCons.Tool.LaTeXSuffixes)

    from . import pdf
    pdf.generate(env)

    bld = env['BUILDERS']['PDF']
    bld.add_action('.ltx', PDFLaTeXAuxAction)
    bld.add_action('.latex', PDFLaTeXAuxAction)
    bld.add_emitter('.ltx', SCons.Tool.tex.tex_pdf_emitter)
    bld.add_emitter('.latex', SCons.Tool.tex.tex_pdf_emitter)

    SCons.Tool.tex.generate_common(env)

def exists(env):
    SCons.Tool.tex.generate_darwin(env)
    return env.Detect('pdflatex')

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/pdftex.py

"""SCons.Tool.pdftex

Tool-specific initialization for pdftex.
Generates .pdf files from .tex files

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import os
import SCons.Action
import SCons.Util
import SCons.Tool.tex

PDFTeXAction = None

# This action might be needed more than once if we are dealing with
# labels and bibtex.
PDFLaTeXAction = None

def PDFLaTeXAuxAction(target = None, source= None, env=None):
    result = SCons.Tool.tex.InternalLaTeXAuxAction( PDFLaTeXAction, target, source, env )
    return result

def PDFTeXLaTeXFunction(target = None, source= None, env=None):
    """A builder for TeX and LaTeX that scans the source file to
    decide the "flavor" of the source and then executes the appropriate
    program."""
    basedir = os.path.split(str(source[0]))[0]
    abspath = os.path.abspath(basedir)

    if SCons.Tool.tex.is_LaTeX(source,env,abspath):
        result = PDFLaTeXAuxAction(target,source,env)
        if result != 0:
            SCons.Tool.tex.check_file_error_message(env['PDFLATEX'])
    else:
        result = PDFTeXAction(target,source,env)
        if result != 0:
            SCons.Tool.tex.check_file_error_message(env['PDFTEX'])
    return result

PDFTeXLaTeXAction = None

def generate(env):
    """Add Builders and construction variables for pdftex to an Environment."""
    global PDFTeXAction
    if PDFTeXAction is None:
        PDFTeXAction = SCons.Action.Action('$PDFTEXCOM', '$PDFTEXCOMSTR')

    global PDFLaTeXAction
    if PDFLaTeXAction is None:
        PDFLaTeXAction = SCons.Action.Action("$PDFLATEXCOM", "$PDFLATEXCOMSTR")

    global PDFTeXLaTeXAction
    if PDFTeXLaTeXAction is None:
        PDFTeXLaTeXAction = SCons.Action.Action(PDFTeXLaTeXFunction,
                              strfunction=SCons.Tool.tex.TeXLaTeXStrFunction)

    env.AppendUnique(LATEXSUFFIXES=SCons.Tool.LaTeXSuffixes)

    from . import pdf
    pdf.generate(env)

    bld = env['BUILDERS']['PDF']
    bld.add_action('.tex', PDFTeXLaTeXAction)
    bld.add_emitter('.tex', SCons.Tool.tex.tex_pdf_emitter)

    # Add the epstopdf builder after the pdftex builder 
    # so pdftex is the default for no source suffix
    pdf.generate2(env)

    SCons.Tool.tex.generate_common(env)

def exists(env):
    SCons.Tool.tex.generate_darwin(env)
    return env.Detect('pdftex')

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/python.py

"""SCons.Tool.python

Registers the Python scanner for the supported Python source file suffixes.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import SCons.Tool
from SCons.Scanner.Python import PythonScanner, PythonSuffixes


def generate(env):
    """Hook the python builder and scanner into the environment."""
    for suffix in PythonSuffixes:
        SCons.Tool.SourceFileScanner.add_scanner(suffix, PythonScanner)


def exists(env):
    return True

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/qt.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""Tool-specific initialization for Qt.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""

import os.path
import re

import SCons.Action
import SCons.Builder
import SCons.Defaults
import SCons.Scanner
import SCons.Tool
import SCons.Util
import SCons.Tool.cxx
import SCons.Warnings
cplusplus = SCons.Tool.cxx

class ToolQtWarning(SCons.Warnings.SConsWarning):
    pass

class GeneratedMocFileNotIncluded(ToolQtWarning):
    pass

class QtdirNotFound(ToolQtWarning):
    pass

SCons.Warnings.enableWarningClass(ToolQtWarning)

header_extensions = [".h", ".hxx", ".hpp", ".hh"]
if SCons.Util.case_sensitive_suffixes('.h', '.H'):
    header_extensions.append('.H')

cxx_suffixes = cplusplus.CXXSuffixes


def find_platform_specific_qt_paths():
    """
    find non-standard QT paths

    If the platform does not put QT tools in standard search paths,
    the path is expected to be set using QTDIR. SCons violates
    the normal rule of not pulling from the user's environment
    in this case.  However, some test cases try to validate what
    happens when QTDIR is unset, so we need to try to make a guess.

    :return: a guess at a path
    """

    # qt_bin_dirs = []
    qt_bin_dir = None
    if os.path.isfile('/etc/redhat-release'):
        with open('/etc/redhat-release','r') as rr:
            lines = rr.readlines()
            distro = lines[0].split()[0]
        if distro == 'CentOS':
            # Centos installs QT under /usr/{lib,lib64}/qt{4,5,-3.3}/bin
            # so we need to handle this differently
            # qt_bin_dirs = glob.glob('/usr/lib64/qt*/bin')
            # TODO: all current Fedoras do the same, need to look deeper here.
            qt_bin_dir = '/usr/lib64/qt-3.3/bin'

    return qt_bin_dir


QT_BIN_DIR = find_platform_specific_qt_paths()

def checkMocIncluded(target, source, env):
    moc = target[0]
    cpp = source[0]
    # looks like cpp.includes is cleared before the build stage :-(
    # not really sure about the path transformations (moc.cwd? cpp.cwd?) :-/
    path = SCons.Defaults.CScan.path(env, moc.cwd)
    includes = SCons.Defaults.CScan(cpp, env, path)
    if moc not in includes:
        SCons.Warnings.warn(
            GeneratedMocFileNotIncluded,
            "Generated moc file '%s' is not included by '%s'" %
            (str(moc), str(cpp)))

def find_file(filename, paths, node_factory):
    for dir in paths:
        node = node_factory(filename, dir)
        if node.rexists():
            return node
    return None

class _Automoc:
    """
    Callable class, which works as an emitter for Programs, SharedLibraries and
    StaticLibraries.
    """

    def __init__(self, objBuilderName):
        self.objBuilderName = objBuilderName

    def __call__(self, target, source, env):
        """
        Smart autoscan function. Gets the list of objects for the Program
        or Lib. Adds objects and builders for the special qt files.
        """
        try:
            if int(env.subst('$QT_AUTOSCAN')) == 0:
                return target, source
        except ValueError:
            pass
        try:
            debug = int(env.subst('$QT_DEBUG'))
        except ValueError:
            debug = 0

        # some shortcuts used in the scanner
        splitext = SCons.Util.splitext
        objBuilder = getattr(env, self.objBuilderName)

        # some regular expressions:
        # Q_OBJECT detection
        q_object_search = re.compile(r'[^A-Za-z0-9]Q_OBJECT[^A-Za-z0-9]')
        # cxx and c comment 'eater'
        #comment = re.compile(r'(//.*)|(/\*(([^*])|(\*[^/]))*\*/)')
        # CW: something must be wrong with the regexp. See also bug #998222
        #     CURRENTLY THERE IS NO TEST CASE FOR THAT

        # The following is kind of hacky to get builders working properly (FIXME)
        objBuilderEnv = objBuilder.env
        objBuilder.env = env
        mocBuilderEnv = env.Moc.env
        env.Moc.env = env

        # make a deep copy for the result; MocH objects will be appended
        out_sources = source[:]

        for obj in source:
            if not obj.has_builder():
                # binary obj file provided
                if debug:
                    print("scons: qt: '%s' seems to be a binary. Discarded." % str(obj))
                continue
            cpp = obj.sources[0]
            if not splitext(str(cpp))[1] in cxx_suffixes:
                if debug:
                    print("scons: qt: '%s' is no cxx file. Discarded." % str(cpp))
                # c or fortran source
                continue
            #cpp_contents = comment.sub('', cpp.get_text_contents())
            if debug:
                print("scons: qt: Getting contents of %s" % cpp)
            cpp_contents = cpp.get_text_contents()
            h=None
            for h_ext in header_extensions:
                # try to find the header file in the corresponding source
                # directory
                hname = splitext(cpp.name)[0] + h_ext
                h = find_file(hname, (cpp.get_dir(),), env.File)
                if h:
                    if debug:
                        print("scons: qt: Scanning '%s' (header of '%s')" % (str(h), str(cpp)))
                    #h_contents = comment.sub('', h.get_text_contents())
                    h_contents = h.get_text_contents()
                    break
            if not h and debug:
                print("scons: qt: no header for '%s'." % (str(cpp)))
            if h and q_object_search.search(h_contents):
                # h file with the Q_OBJECT macro found -> add moc_cpp
                moc_cpp = env.Moc(h)
                moc_o = objBuilder(moc_cpp)
                out_sources.append(moc_o)
                #moc_cpp.target_scanner = SCons.Defaults.CScan
                if debug:
                    print("scons: qt: found Q_OBJECT macro in '%s', moc'ing to '%s'" % (str(h), str(moc_cpp)))
            if cpp and q_object_search.search(cpp_contents):
                # cpp file with Q_OBJECT macro found -> add moc
                # (to be included in cpp)
                moc = env.Moc(cpp)
                env.Ignore(moc, moc)
                if debug:
                    print("scons: qt: found Q_OBJECT macro in '%s', moc'ing to '%s'" % (str(cpp), str(moc)))
                #moc.source_scanner = SCons.Defaults.CScan
        # restore the original env attributes (FIXME)
        objBuilder.env = objBuilderEnv
        env.Moc.env = mocBuilderEnv

        return (target, out_sources)

AutomocShared = _Automoc('SharedObject')
AutomocStatic = _Automoc('StaticObject')

def _detect(env):
    """Not really safe, but fast method to detect the QT library"""

    QTDIR = env.get('QTDIR',None)
    if not QTDIR:
        QTDIR = os.environ.get('QTDIR',None)
    if not QTDIR:
        moc = env.WhereIs('moc') or env.WhereIs('moc',QT_BIN_DIR)
        if moc:
            QTDIR = os.path.dirname(os.path.dirname(moc))
            SCons.Warnings.warn(
                QtdirNotFound,
                "Could not detect qt, using moc executable as a hint (QTDIR=%s)" % QTDIR)
        else:
            QTDIR = None
            SCons.Warnings.warn(
                QtdirNotFound,
                "Could not detect qt, using empty QTDIR")
    return QTDIR

def uicEmitter(target, source, env):
    adjustixes = SCons.Util.adjustixes
    bs = SCons.Util.splitext(str(source[0].name))[0]
    bs = os.path.join(str(target[0].get_dir()),bs)
    # first target (header) is automatically added by builder
    if len(target) < 2:
        # second target is implementation
        target.append(adjustixes(bs,
                                 env.subst('$QT_UICIMPLPREFIX'),
                                 env.subst('$QT_UICIMPLSUFFIX')))
    if len(target) < 3:
        # third target is moc file
        target.append(adjustixes(bs,
                                 env.subst('$QT_MOCHPREFIX'),
                                 env.subst('$QT_MOCHSUFFIX')))
    return target, source

def uicScannerFunc(node, env, path):
    lookout = []
    lookout.extend(env['CPPPATH'])
    lookout.append(str(node.rfile().dir))
    includes = re.findall("<include.*?>(.*?)</include>", node.get_text_contents())
    result = []
    for incFile in includes:
        dep = env.FindFile(incFile,lookout)
        if dep:
            result.append(dep)
    return result

uicScanner = SCons.Scanner.ScannerBase(uicScannerFunc,
                                name = "UicScanner",
                                node_class = SCons.Node.FS.File,
                                node_factory = SCons.Node.FS.File,
                                recursive = 0)

def generate(env):
    """Add Builders and construction variables for qt to an Environment."""
    CLVar = SCons.Util.CLVar
    Action = SCons.Action.Action
    Builder = SCons.Builder.Builder

    SCons.Warnings.warn(
        SCons.Warnings.ToolQtDeprecatedWarning, "Tool module for Qt version 3 is deprecated"
    )

    env.SetDefault(QTDIR  = _detect(env),
                   QT_BINPATH = os.path.join('$QTDIR', 'bin'),
                   QT_CPPPATH = os.path.join('$QTDIR', 'include'),
                   QT_LIBPATH = os.path.join('$QTDIR', 'lib'),
                   QT_MOC = os.path.join('$QT_BINPATH','moc'),
                   QT_UIC = os.path.join('$QT_BINPATH','uic'),
                   QT_LIB = 'qt', # may be set to qt-mt

                   QT_AUTOSCAN = 1, # scan for moc'able sources

                   # Some QT specific flags. I don't expect someone wants to
                   # manipulate those ...
                   QT_UICIMPLFLAGS = CLVar(''),
                   QT_UICDECLFLAGS = CLVar(''),
                   QT_MOCFROMHFLAGS = CLVar(''),
                   QT_MOCFROMCXXFLAGS = CLVar('-i'),

                   # suffixes/prefixes for the headers / sources to generate
                   QT_UICDECLPREFIX = '',
                   QT_UICDECLSUFFIX = '.h',
                   QT_UICIMPLPREFIX = 'uic_',
                   QT_UICIMPLSUFFIX = '$CXXFILESUFFIX',
                   QT_MOCHPREFIX = 'moc_',
                   QT_MOCHSUFFIX = '$CXXFILESUFFIX',
                   QT_MOCCXXPREFIX = '',
                   QT_MOCCXXSUFFIX = '.moc',
                   QT_UISUFFIX = '.ui',

                   # Commands for the qt support ...
                   # command to generate header, implementation and moc-file
                   # from a .ui file
                   QT_UICCOM = [
                    CLVar('$QT_UIC $QT_UICDECLFLAGS -o ${TARGETS[0]} $SOURCE'),
                    CLVar('$QT_UIC $QT_UICIMPLFLAGS -impl ${TARGETS[0].file} '
                          '-o ${TARGETS[1]} $SOURCE'),
                    CLVar('$QT_MOC $QT_MOCFROMHFLAGS -o ${TARGETS[2]} ${TARGETS[0]}')],
                   # command to generate meta object information for a class
                   # declarated in a header
                   QT_MOCFROMHCOM = (
                          '$QT_MOC $QT_MOCFROMHFLAGS -o ${TARGETS[0]} $SOURCE'),
                   # command to generate meta object information for a class
                   # declarated in a cpp file
                   QT_MOCFROMCXXCOM = [
                    CLVar('$QT_MOC $QT_MOCFROMCXXFLAGS -o ${TARGETS[0]} $SOURCE'),
                    Action(checkMocIncluded,None)])

    # ... and the corresponding builders
    uicBld = Builder(action=SCons.Action.Action('$QT_UICCOM', '$QT_UICCOMSTR'),
                     emitter=uicEmitter,
                     src_suffix='$QT_UISUFFIX',
                     suffix='$QT_UICDECLSUFFIX',
                     prefix='$QT_UICDECLPREFIX',
                     source_scanner=uicScanner)
    mocBld = Builder(action={}, prefix={}, suffix={})
    for h in header_extensions:
        act = SCons.Action.Action('$QT_MOCFROMHCOM', '$QT_MOCFROMHCOMSTR')
        mocBld.add_action(h, act)
        mocBld.prefix[h] = '$QT_MOCHPREFIX'
        mocBld.suffix[h] = '$QT_MOCHSUFFIX'
    for cxx in cxx_suffixes:
        act = SCons.Action.Action('$QT_MOCFROMCXXCOM', '$QT_MOCFROMCXXCOMSTR')
        mocBld.add_action(cxx, act)
        mocBld.prefix[cxx] = '$QT_MOCCXXPREFIX'
        mocBld.suffix[cxx] = '$QT_MOCCXXSUFFIX'

    # register the builders
    env['BUILDERS']['Uic'] = uicBld
    env['BUILDERS']['Moc'] = mocBld
    static_obj, shared_obj = SCons.Tool.createObjBuilders(env)
    static_obj.add_src_builder('Uic')
    shared_obj.add_src_builder('Uic')

    # We use the emitters of Program / StaticLibrary / SharedLibrary
    # to scan for moc'able files
    # We can't refer to the builders directly, we have to fetch them
    # as Environment attributes because that sets them up to be called
    # correctly later by our emitter.
    env.AppendUnique(PROGEMITTER =[AutomocStatic],
                     SHLIBEMITTER=[AutomocShared],
                     LDMODULEEMITTER=[AutomocShared],
                     LIBEMITTER  =[AutomocStatic],
                     # Of course, we need to link against the qt libraries
                     CPPPATH=["$QT_CPPPATH"],
                     LIBPATH=["$QT_LIBPATH"],
                     LIBS=['$QT_LIB'])

def exists(env):
    return _detect(env)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/rmic.py

"""SCons.Tool.rmic

Tool-specific initialization for rmic.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import os.path

import SCons.Action
import SCons.Builder
import SCons.Node.FS
import SCons.Util

from SCons.Tool.JavaCommon import get_java_install_dirs


def emit_rmic_classes(target, source, env):
    """Create and return lists of Java RMI stub and skeleton
    class files to be created from a set of class files.
    """
    class_suffix = env.get('JAVACLASSSUFFIX', '.class')
    classdir = env.get('JAVACLASSDIR')

    if not classdir:
        try:
            s = source[0]
        except IndexError:
            classdir = '.'
        else:
            try:
                classdir = s.attributes.java_classdir
            except AttributeError:
                classdir = '.'
    classdir = env.Dir(classdir).rdir()
    if str(classdir) == '.':
        c_ = None
    else:
        c_ = str(classdir) + os.sep

    slist = []
    for src in source:
        try:
            classname = src.attributes.java_classname
        except AttributeError:
            classname = str(src)
            if c_ and classname[:len(c_)] == c_:
                classname = classname[len(c_):]
            if class_suffix and classname[:-len(class_suffix)] == class_suffix:
                classname = classname[-len(class_suffix):]
        s = src.rfile()
        s.attributes.java_classdir = classdir
        s.attributes.java_classname = classname
        slist.append(s)

    stub_suffixes = ['_Stub']
    if env.get('JAVAVERSION') == '1.4':
        stub_suffixes.append('_Skel')

    tlist = []
    for s in source:
        for suff in stub_suffixes:
            fname = s.attributes.java_classname.replace('.', os.sep) + \
                    suff + class_suffix
            t = target[0].File(fname)
            t.attributes.java_lookupdir = target[0]
            tlist.append(t)

    return tlist, source

RMICAction = SCons.Action.Action('$RMICCOM', '$RMICCOMSTR')

RMICBuilder = SCons.Builder.Builder(action = RMICAction,
                     emitter = emit_rmic_classes,
                     src_suffix = '$JAVACLASSSUFFIX',
                     target_factory = SCons.Node.FS.Dir,
                     source_factory = SCons.Node.FS.File)

def generate(env):
    """Add Builders and construction variables for rmic to an Environment."""
    env['BUILDERS']['RMIC'] = RMICBuilder

    if env['PLATFORM'] == 'win32':
        version = env.get('JAVAVERSION', None)
        # Ensure that we have a proper path for rmic
        paths = get_java_install_dirs('win32', version=version)
        rmic = SCons.Tool.find_program_path(env, 'rmic', default_paths=paths)
        # print("RMIC: %s"%rmic)
        if rmic:
            rmic_bin_dir = os.path.dirname(rmic)
            env.AppendENVPath('PATH', rmic_bin_dir)

    env['RMIC']            = 'rmic'
    env['RMICFLAGS']       = SCons.Util.CLVar('')
    env['RMICCOM']         = '$RMIC $RMICFLAGS -d ${TARGET.attributes.java_lookupdir} -classpath ${SOURCE.attributes.java_classdir} ${SOURCES.attributes.java_classname}'
    env['JAVACLASSSUFFIX']  = '.class'

def exists(env):
    # As reported by Jan Nijtmans in issue #2730, the simple
    #    return env.Detect('rmic')
    # doesn't always work during initialization. For now, we
    # stop trying to detect an executable (analogous to the
    # javac Builder).
    # TODO: Come up with a proper detect() routine...and enable it.
    return 1

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/rpcgen.py

"""SCons.Tool.rpcgen

Tool-specific initialization for RPCGEN tools.

Three normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

from SCons.Builder import Builder
import SCons.Util

cmd = "cd ${SOURCE.dir} && $RPCGEN -%s $RPCGENFLAGS %s -o ${TARGET.abspath} ${SOURCE.file}"

rpcgen_client   = cmd % ('l', '$RPCGENCLIENTFLAGS')
rpcgen_header   = cmd % ('h', '$RPCGENHEADERFLAGS')
rpcgen_service  = cmd % ('m', '$RPCGENSERVICEFLAGS')
rpcgen_xdr      = cmd % ('c', '$RPCGENXDRFLAGS')

def generate(env):
    """Add RPCGEN Builders and construction variables for an Environment."""
    
    client  = Builder(action=rpcgen_client,  suffix='_clnt.c', src_suffix='.x')
    header  = Builder(action=rpcgen_header,  suffix='.h',      src_suffix='.x')
    service = Builder(action=rpcgen_service, suffix='_svc.c',  src_suffix='.x')
    xdr     = Builder(action=rpcgen_xdr,     suffix='_xdr.c',  src_suffix='.x')
    env.Append(BUILDERS={'RPCGenClient'  : client,
                         'RPCGenHeader'  : header,
                         'RPCGenService' : service,
                         'RPCGenXDR'     : xdr})
    env['RPCGEN'] = 'rpcgen'
    env['RPCGENFLAGS'] = SCons.Util.CLVar('')
    env['RPCGENCLIENTFLAGS'] = SCons.Util.CLVar('')
    env['RPCGENHEADERFLAGS'] = SCons.Util.CLVar('')
    env['RPCGENSERVICEFLAGS'] = SCons.Util.CLVar('')
    env['RPCGENXDRFLAGS'] = SCons.Util.CLVar('')

def exists(env):
    return env.Detect('rpcgen')

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/rpm.py

"""SCons.Tool.rpm

Tool-specific initialization for rpm.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

The rpm tool calls the rpmbuild command. The first and only argument should a
tar.gz consisting of the source file and a specfile.
"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import os
import re
import shutil
import subprocess

import SCons.Builder
import SCons.Node.FS
import SCons.Util
import SCons.Action
import SCons.Defaults

def get_cmd(source, env):
    tar_file_with_included_specfile = source
    if SCons.Util.is_List(source):
        tar_file_with_included_specfile = source[0]
    return "%s %s %s"%(env['RPM'], env['RPMFLAGS'],
                       tar_file_with_included_specfile.get_abspath())

def build_rpm(target, source, env):
    # create a temporary rpm build root.
    tmpdir = os.path.join(os.path.dirname(target[0].get_abspath()), 'rpmtemp')
    if os.path.exists(tmpdir):
        shutil.rmtree(tmpdir)

    # now create the mandatory rpm directory structure.
    for d in ['RPMS', 'SRPMS', 'SPECS', 'BUILD']:
        os.makedirs(os.path.join(tmpdir, d))

    # set the topdir as an rpmflag.
    env.Prepend(RPMFLAGS = '--define \'_topdir %s\'' % tmpdir)

    # now call rpmbuild to create the rpm package.
    handle  = subprocess.Popen(get_cmd(source, env),
                               stdout=subprocess.PIPE,
                               stderr=subprocess.STDOUT,
                               shell=True)
    with handle.stdout:
        output = SCons.Util.to_str(handle.stdout.read())
    status = handle.wait()

    if status:
        raise SCons.Errors.BuildError(node=target[0],
                                      errstr=output,
                                      filename=str(target[0]))
    else:
        # XXX: assume that LC_ALL=C is set while running rpmbuild
        output_files = re.compile('Wrote: (.*)').findall(output)

        for output, input in zip(output_files, target):
            rpm_output = os.path.basename(output)
            expected   = os.path.basename(input.get_path())

            assert expected == rpm_output, "got %s but expected %s" % (rpm_output, expected)
            shutil.copy(output, input.get_abspath())


    # cleanup before leaving.
    shutil.rmtree(tmpdir)

    return status

def string_rpm(target, source, env):
    try:
        return env['RPMCOMSTR']
    except KeyError:
        return get_cmd(source, env)

rpmAction = SCons.Action.Action(build_rpm, string_rpm)

RpmBuilder = SCons.Builder.Builder(action = SCons.Action.Action('$RPMCOM', '$RPMCOMSTR'),
                                   source_scanner = SCons.Defaults.DirScanner,
                                   suffix = '$RPMSUFFIX')



def generate(env):
    """Add Builders and construction variables for rpm to an Environment."""
    try:
        bld = env['BUILDERS']['Rpm']
    except KeyError:
        bld = RpmBuilder
        env['BUILDERS']['Rpm'] = bld

    env.SetDefault(RPM          = 'LC_ALL=C rpmbuild')
    env.SetDefault(RPMFLAGS     = SCons.Util.CLVar('-ta'))
    env.SetDefault(RPMCOM       = rpmAction)
    env.SetDefault(RPMSUFFIX    = '.rpm')

def exists(env):
    return env.Detect('rpmbuild')

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/rpmutils.py

"""SCons.Tool.rpmutils.py

RPM specific helper routines for general usage in the test framework
and SCons core modules.

Since we check for the RPM package target name in several places,
we have to know which machine/system name RPM will use for the current
hardware setup. The following dictionaries and functions try to
mimic the exact naming rules of the RPM source code.
They were directly derived from the file "rpmrc.in" of the version
rpm-4.9.1.3. For updating to a more recent version of RPM, this Python
script can be used standalone. The usage() function below shows the
exact syntax.

"""

# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"


import platform
import subprocess

import SCons.Util

# Start of rpmrc dictionaries (Marker, don't change or remove!)
os_canon = {
  'AIX' : ['AIX','5'],
  'AmigaOS' : ['AmigaOS','5'],
  'BSD_OS' : ['bsdi','12'],
  'CYGWIN32_95' : ['cygwin32','15'],
  'CYGWIN32_NT' : ['cygwin32','14'],
  'Darwin' : ['darwin','21'],
  'FreeBSD' : ['FreeBSD','8'],
  'HP-UX' : ['hpux10','6'],
  'IRIX' : ['Irix','2'],
  'IRIX64' : ['Irix64','10'],
  'Linux' : ['Linux','1'],
  'Linux/390' : ['OS/390','20'],
  'Linux/ESA' : ['VM/ESA','20'],
  'MacOSX' : ['macosx','21'],
  'MiNT' : ['FreeMiNT','17'],
  'NEXTSTEP' : ['NextStep','11'],
  'OS/390' : ['OS/390','18'],
  'OSF1' : ['osf1','7'],
  'SCO_SV' : ['SCO_SV3.2v5.0.2','9'],
  'SunOS4' : ['SunOS','4'],
  'SunOS5' : ['solaris','3'],
  'UNIX_SV' : ['MP_RAS','16'],
  'VM/ESA' : ['VM/ESA','19'],
  'machten' : ['machten','13'],
  'osf3.2' : ['osf1','7'],
  'osf4.0' : ['osf1','7'],
}

buildarch_compat = {
  'alpha' : ['noarch'],
  'alphaev5' : ['alpha'],
  'alphaev56' : ['alphaev5'],
  'alphaev6' : ['alphapca56'],
  'alphaev67' : ['alphaev6'],
  'alphapca56' : ['alphaev56'],
  'amd64' : ['x86_64'],
  'armv3l' : ['noarch'],
  'armv4b' : ['noarch'],
  'armv4l' : ['armv3l'],
  'armv4tl' : ['armv4l'],
  'armv5tejl' : ['armv5tel'],
  'armv5tel' : ['armv4tl'],
  'armv6l' : ['armv5tejl'],
  'armv7l' : ['armv6l'],
  'atariclone' : ['m68kmint','noarch'],
  'atarist' : ['m68kmint','noarch'],
  'atariste' : ['m68kmint','noarch'],
  'ataritt' : ['m68kmint','noarch'],
  'athlon' : ['i686'],
  'falcon' : ['m68kmint','noarch'],
  'geode' : ['i586'],
  'hades' : ['m68kmint','noarch'],
  'hppa1.0' : ['parisc'],
  'hppa1.1' : ['hppa1.0'],
  'hppa1.2' : ['hppa1.1'],
  'hppa2.0' : ['hppa1.2'],
  'i386' : ['noarch','fat'],
  'i486' : ['i386'],
  'i586' : ['i486'],
  'i686' : ['i586'],
  'ia32e' : ['x86_64'],
  'ia64' : ['noarch'],
  'm68k' : ['noarch'],
  'milan' : ['m68kmint','noarch'],
  'mips' : ['noarch'],
  'mipsel' : ['noarch'],
  'parisc' : ['noarch'],
  'pentium3' : ['i686'],
  'pentium4' : ['pentium3'],
  'ppc' : ['noarch','fat'],
  'ppc32dy4' : ['noarch'],
  'ppc64' : ['noarch','fat'],
  'ppc64iseries' : ['ppc64'],
  'ppc64pseries' : ['ppc64'],
  'ppc8260' : ['noarch'],
  'ppc8560' : ['noarch'],
  'ppciseries' : ['noarch'],
  'ppcpseries' : ['noarch'],
  's390' : ['noarch'],
  's390x' : ['noarch'],
  'sh3' : ['noarch'],
  'sh4' : ['noarch'],
  'sh4a' : ['sh4'],
  'sparc' : ['noarch'],
  'sparc64' : ['sparcv9v'],
  'sparc64v' : ['sparc64'],
  'sparcv8' : ['sparc'],
  'sparcv9' : ['sparcv8'],
  'sparcv9v' : ['sparcv9'],
  'sun4c' : ['noarch'],
  'sun4d' : ['noarch'],
  'sun4m' : ['noarch'],
  'sun4u' : ['noarch'],
  'x86_64' : ['noarch'],
}

os_compat = {
  'BSD_OS' : ['bsdi'],
  'Darwin' : ['MacOSX'],
  'FreeMiNT' : ['mint','MiNT','TOS'],
  'IRIX64' : ['IRIX'],
  'MiNT' : ['FreeMiNT','mint','TOS'],
  'TOS' : ['FreeMiNT','MiNT','mint'],
  'bsdi4.0' : ['bsdi'],
  'hpux10.00' : ['hpux9.07'],
  'hpux10.01' : ['hpux10.00'],
  'hpux10.10' : ['hpux10.01'],
  'hpux10.20' : ['hpux10.10'],
  'hpux10.30' : ['hpux10.20'],
  'hpux11.00' : ['hpux10.30'],
  'hpux9.05' : ['hpux9.04'],
  'hpux9.07' : ['hpux9.05'],
  'mint' : ['FreeMiNT','MiNT','TOS'],
  'ncr-sysv4.3' : ['ncr-sysv4.2'],
  'osf4.0' : ['osf3.2','osf1'],
  'solaris2.4' : ['solaris2.3'],
  'solaris2.5' : ['solaris2.3','solaris2.4'],
  'solaris2.6' : ['solaris2.3','solaris2.4','solaris2.5'],
  'solaris2.7' : ['solaris2.3','solaris2.4','solaris2.5','solaris2.6'],
}

arch_compat = {
  'alpha' : ['axp','noarch'],
  'alphaev5' : ['alpha'],
  'alphaev56' : ['alphaev5'],
  'alphaev6' : ['alphapca56'],
  'alphaev67' : ['alphaev6'],
  'alphapca56' : ['alphaev56'],
  'amd64' : ['x86_64','athlon','noarch'],
  'armv3l' : ['noarch'],
  'armv4b' : ['noarch'],
  'armv4l' : ['armv3l'],
  'armv4tl' : ['armv4l'],
  'armv5tejl' : ['armv5tel'],
  'armv5tel' : ['armv4tl'],
  'armv6l' : ['armv5tejl'],
  'armv7l' : ['armv6l'],
  'atariclone' : ['m68kmint','noarch'],
  'atarist' : ['m68kmint','noarch'],
  'atariste' : ['m68kmint','noarch'],
  'ataritt' : ['m68kmint','noarch'],
  'athlon' : ['i686'],
  'falcon' : ['m68kmint','noarch'],
  'geode' : ['i586'],
  'hades' : ['m68kmint','noarch'],
  'hppa1.0' : ['parisc'],
  'hppa1.1' : ['hppa1.0'],
  'hppa1.2' : ['hppa1.1'],
  'hppa2.0' : ['hppa1.2'],
  'i370' : ['noarch'],
  'i386' : ['noarch','fat'],
  'i486' : ['i386'],
  'i586' : ['i486'],
  'i686' : ['i586'],
  'ia32e' : ['x86_64','athlon','noarch'],
  'ia64' : ['noarch'],
  'milan' : ['m68kmint','noarch'],
  'mips' : ['noarch'],
  'mipsel' : ['noarch'],
  'osfmach3_i386' : ['i486'],
  'osfmach3_i486' : ['i486','osfmach3_i386'],
  'osfmach3_i586' : ['i586','osfmach3_i486'],
  'osfmach3_i686' : ['i686','osfmach3_i586'],
  'osfmach3_ppc' : ['ppc'],
  'parisc' : ['noarch'],
  'pentium3' : ['i686'],
  'pentium4' : ['pentium3'],
  'powerpc' : ['ppc'],
  'powerppc' : ['ppc'],
  'ppc' : ['rs6000'],
  'ppc32dy4' : ['ppc'],
  'ppc64' : ['ppc'],
  'ppc64iseries' : ['ppc64'],
  'ppc64pseries' : ['ppc64'],
  'ppc8260' : ['ppc'],
  'ppc8560' : ['ppc'],
  'ppciseries' : ['ppc'],
  'ppcpseries' : ['ppc'],
  'rs6000' : ['noarch','fat'],
  's390' : ['noarch'],
  's390x' : ['s390','noarch'],
  'sh3' : ['noarch'],
  'sh4' : ['noarch'],
  'sh4a' : ['sh4'],
  'sparc' : ['noarch'],
  'sparc64' : ['sparcv9'],
  'sparc64v' : ['sparc64'],
  'sparcv8' : ['sparc'],
  'sparcv9' : ['sparcv8'],
  'sparcv9v' : ['sparcv9'],
  'sun4c' : ['sparc'],
  'sun4d' : ['sparc'],
  'sun4m' : ['sparc'],
  'sun4u' : ['sparc64'],
  'x86_64' : ['amd64','athlon','noarch'],
}

buildarchtranslate = {
  'alphaev5' : ['alpha'],
  'alphaev56' : ['alpha'],
  'alphaev6' : ['alpha'],
  'alphaev67' : ['alpha'],
  'alphapca56' : ['alpha'],
  'amd64' : ['x86_64'],
  'armv3l' : ['armv3l'],
  'armv4b' : ['armv4b'],
  'armv4l' : ['armv4l'],
  'armv4tl' : ['armv4tl'],
  'armv5tejl' : ['armv5tejl'],
  'armv5tel' : ['armv5tel'],
  'armv6l' : ['armv6l'],
  'armv7l' : ['armv7l'],
  'atariclone' : ['m68kmint'],
  'atarist' : ['m68kmint'],
  'atariste' : ['m68kmint'],
  'ataritt' : ['m68kmint'],
  'athlon' : ['i386'],
  'falcon' : ['m68kmint'],
  'geode' : ['i386'],
  'hades' : ['m68kmint'],
  'i386' : ['i386'],
  'i486' : ['i386'],
  'i586' : ['i386'],
  'i686' : ['i386'],
  'ia32e' : ['x86_64'],
  'ia64' : ['ia64'],
  'milan' : ['m68kmint'],
  'osfmach3_i386' : ['i386'],
  'osfmach3_i486' : ['i386'],
  'osfmach3_i586' : ['i386'],
  'osfmach3_i686' : ['i386'],
  'osfmach3_ppc' : ['ppc'],
  'pentium3' : ['i386'],
  'pentium4' : ['i386'],
  'powerpc' : ['ppc'],
  'powerppc' : ['ppc'],
  'ppc32dy4' : ['ppc'],
  'ppc64iseries' : ['ppc64'],
  'ppc64pseries' : ['ppc64'],
  'ppc8260' : ['ppc'],
  'ppc8560' : ['ppc'],
  'ppciseries' : ['ppc'],
  'ppcpseries' : ['ppc'],
  's390' : ['s390'],
  's390x' : ['s390x'],
  'sh3' : ['sh3'],
  'sh4' : ['sh4'],
  'sh4a' : ['sh4'],
  'sparc64v' : ['sparc64'],
  'sparcv8' : ['sparc'],
  'sparcv9' : ['sparc'],
  'sparcv9v' : ['sparc'],
  'sun4c' : ['sparc'],
  'sun4d' : ['sparc'],
  'sun4m' : ['sparc'],
  'sun4u' : ['sparc64'],
  'x86_64' : ['x86_64'],
}

optflags = {
  'alpha' : ['-O2','-g','-mieee'],
  'alphaev5' : ['-O2','-g','-mieee','-mtune=ev5'],
  'alphaev56' : ['-O2','-g','-mieee','-mtune=ev56'],
  'alphaev6' : ['-O2','-g','-mieee','-mtune=ev6'],
  'alphaev67' : ['-O2','-g','-mieee','-mtune=ev67'],
  'alphapca56' : ['-O2','-g','-mieee','-mtune=pca56'],
  'amd64' : ['-O2','-g'],
  'armv3l' : ['-O2','-g','-march=armv3'],
  'armv4b' : ['-O2','-g','-march=armv4'],
  'armv4l' : ['-O2','-g','-march=armv4'],
  'armv4tl' : ['-O2','-g','-march=armv4t'],
  'armv5tejl' : ['-O2','-g','-march=armv5te'],
  'armv5tel' : ['-O2','-g','-march=armv5te'],
  'armv6l' : ['-O2','-g','-march=armv6'],
  'armv7l' : ['-O2','-g','-march=armv7'],
  'atariclone' : ['-O2','-g','-fomit-frame-pointer'],
  'atarist' : ['-O2','-g','-fomit-frame-pointer'],
  'atariste' : ['-O2','-g','-fomit-frame-pointer'],
  'ataritt' : ['-O2','-g','-fomit-frame-pointer'],
  'athlon' : ['-O2','-g','-march=athlon'],
  'falcon' : ['-O2','-g','-fomit-frame-pointer'],
  'fat' : ['-O2','-g','-arch','i386','-arch','ppc'],
  'geode' : ['-Os','-g','-m32','-march=geode'],
  'hades' : ['-O2','-g','-fomit-frame-pointer'],
  'hppa1.0' : ['-O2','-g','-mpa-risc-1-0'],
  'hppa1.1' : ['-O2','-g','-mpa-risc-1-0'],
  'hppa1.2' : ['-O2','-g','-mpa-risc-1-0'],
  'hppa2.0' : ['-O2','-g','-mpa-risc-1-0'],
  'i386' : ['-O2','-g','-march=i386','-mtune=i686'],
  'i486' : ['-O2','-g','-march=i486'],
  'i586' : ['-O2','-g','-march=i586'],
  'i686' : ['-O2','-g','-march=i686'],
  'ia32e' : ['-O2','-g'],
  'ia64' : ['-O2','-g'],
  'm68k' : ['-O2','-g','-fomit-frame-pointer'],
  'milan' : ['-O2','-g','-fomit-frame-pointer'],
  'mips' : ['-O2','-g'],
  'mipsel' : ['-O2','-g'],
  'parisc' : ['-O2','-g','-mpa-risc-1-0'],
  'pentium3' : ['-O2','-g','-march=pentium3'],
  'pentium4' : ['-O2','-g','-march=pentium4'],
  'ppc' : ['-O2','-g','-fsigned-char'],
  'ppc32dy4' : ['-O2','-g','-fsigned-char'],
  'ppc64' : ['-O2','-g','-fsigned-char'],
  'ppc8260' : ['-O2','-g','-fsigned-char'],
  'ppc8560' : ['-O2','-g','-fsigned-char'],
  'ppciseries' : ['-O2','-g','-fsigned-char'],
  'ppcpseries' : ['-O2','-g','-fsigned-char'],
  's390' : ['-O2','-g'],
  's390x' : ['-O2','-g'],
  'sh3' : ['-O2','-g'],
  'sh4' : ['-O2','-g','-mieee'],
  'sh4a' : ['-O2','-g','-mieee'],
  'sparc' : ['-O2','-g','-m32','-mtune=ultrasparc'],
  'sparc64' : ['-O2','-g','-m64','-mtune=ultrasparc'],
  'sparc64v' : ['-O2','-g','-m64','-mtune=niagara'],
  'sparcv8' : ['-O2','-g','-m32','-mtune=ultrasparc','-mv8'],
  'sparcv9' : ['-O2','-g','-m32','-mtune=ultrasparc'],
  'sparcv9v' : ['-O2','-g','-m32','-mtune=niagara'],
  'x86_64' : ['-O2','-g'],
}

arch_canon = {
  'IP' : ['sgi','7'],
  'alpha' : ['alpha','2'],
  'alphaev5' : ['alphaev5','2'],
  'alphaev56' : ['alphaev56','2'],
  'alphaev6' : ['alphaev6','2'],
  'alphaev67' : ['alphaev67','2'],
  'alphapca56' : ['alphapca56','2'],
  'amd64' : ['amd64','1'],
  'armv3l' : ['armv3l','12'],
  'armv4b' : ['armv4b','12'],
  'armv4l' : ['armv4l','12'],
  'armv5tejl' : ['armv5tejl','12'],
  'armv5tel' : ['armv5tel','12'],
  'armv6l' : ['armv6l','12'],
  'armv7l' : ['armv7l','12'],
  'atariclone' : ['m68kmint','13'],
  'atarist' : ['m68kmint','13'],
  'atariste' : ['m68kmint','13'],
  'ataritt' : ['m68kmint','13'],
  'athlon' : ['athlon','1'],
  'falcon' : ['m68kmint','13'],
  'geode' : ['geode','1'],
  'hades' : ['m68kmint','13'],
  'i370' : ['i370','14'],
  'i386' : ['i386','1'],
  'i486' : ['i486','1'],
  'i586' : ['i586','1'],
  'i686' : ['i686','1'],
  'ia32e' : ['ia32e','1'],
  'ia64' : ['ia64','9'],
  'm68k' : ['m68k','6'],
  'm68kmint' : ['m68kmint','13'],
  'milan' : ['m68kmint','13'],
  'mips' : ['mips','4'],
  'mipsel' : ['mipsel','11'],
  'pentium3' : ['pentium3','1'],
  'pentium4' : ['pentium4','1'],
  'ppc' : ['ppc','5'],
  'ppc32dy4' : ['ppc32dy4','5'],
  'ppc64' : ['ppc64','16'],
  'ppc64iseries' : ['ppc64iseries','16'],
  'ppc64pseries' : ['ppc64pseries','16'],
  'ppc8260' : ['ppc8260','5'],
  'ppc8560' : ['ppc8560','5'],
  'ppciseries' : ['ppciseries','5'],
  'ppcpseries' : ['ppcpseries','5'],
  'rs6000' : ['rs6000','8'],
  's390' : ['s390','14'],
  's390x' : ['s390x','15'],
  'sh' : ['sh','17'],
  'sh3' : ['sh3','17'],
  'sh4' : ['sh4','17'],
  'sh4a' : ['sh4a','17'],
  'sparc' : ['sparc','3'],
  'sparc64' : ['sparc64','2'],
  'sparc64v' : ['sparc64v','2'],
  'sparcv8' : ['sparcv8','3'],
  'sparcv9' : ['sparcv9','3'],
  'sparcv9v' : ['sparcv9v','3'],
  'sun4' : ['sparc','3'],
  'sun4c' : ['sparc','3'],
  'sun4d' : ['sparc','3'],
  'sun4m' : ['sparc','3'],
  'sun4u' : ['sparc64','2'],
  'x86_64' : ['x86_64','1'],
  'xtensa' : ['xtensa','18'],
}

# End of rpmrc dictionaries (Marker, don't change or remove!)

def defaultMachine(use_rpm_default=True):
    """ Return the canonicalized machine name. """

    if use_rpm_default:
        try:
            # This should be the most reliable way to get the default arch
            rmachine = subprocess.check_output(['rpm', '--eval=%_target_cpu'], shell=False).rstrip()
            rmachine = SCons.Util.to_str(rmachine)
        except Exception as e:
            # Something went wrong, try again by looking up platform.machine()
            return defaultMachine(False)
    else:
        rmachine = platform.machine()

        # Try to lookup the string in the canon table
        if rmachine in arch_canon:
            rmachine = arch_canon[rmachine][0]

    return rmachine

def defaultSystem():
    """ Return the canonicalized system name. """
    rsystem = platform.system()

    # Try to lookup the string in the canon tables
    if rsystem in os_canon:
        rsystem = os_canon[rsystem][0]

    return rsystem

def defaultNames():
    """ Return the canonicalized machine and system name. """
    return defaultMachine(), defaultSystem()

def updateRpmDicts(rpmrc, pyfile):
    """ Read the given rpmrc file with RPM definitions and update the
        info dictionaries in the file pyfile with it.
        The arguments will usually be 'rpmrc.in' from a recent RPM source
        tree, and 'rpmutils.py' referring to this script itself.
        See also usage() below.
    """
    try:
        # Read old rpmutils.py file
        with open(pyfile,"r") as f:
            oldpy = f.readlines()
        # Read current rpmrc.in file
        with open(rpmrc,"r") as f:
            rpm = f.readlines()
        # Parse for data
        data = {}
        # Allowed section names that get parsed
        sections = ['optflags',
                    'arch_canon',
                    'os_canon',
                    'buildarchtranslate',
                    'arch_compat',
                    'os_compat',
                    'buildarch_compat']
        for l in rpm:
            l = l.rstrip('\n').replace(':',' ')
            # Skip comments
            if l.lstrip().startswith('#'):
                continue
            tokens = l.strip().split()
            if len(tokens):
                key = tokens[0]
                if key in sections:
                    # Have we met this section before?
                    if tokens[0] not in data:
                        # No, so insert it
                        data[key] = {}
                    # Insert data
                    data[key][tokens[1]] = tokens[2:]
        # Write new rpmutils.py file
        with open(pyfile,"w") as out:
            pm = 0
            for l in oldpy:
                if pm:
                    if l.startswith('# End of rpmrc dictionaries'):
                        pm = 0
                        out.write(l)
                else:
                    out.write(l)
                    if l.startswith('# Start of rpmrc dictionaries'):
                        pm = 1
                        # Write data sections to single dictionaries
                        for key, entries in data.items():
                            out.write("%s = {\n" % key)
                            for arch in sorted(entries.keys()):
                                out.write("  '%s' : ['%s'],\n" % (arch, "','".join(entries[arch])))
                            out.write("}\n\n")
    except:
        pass

def usage():
    print("rpmutils.py rpmrc.in rpmutils.py")

def main():
    import sys

    if len(sys.argv) < 3:
        usage()
        sys.exit(0)
    updateRpmDicts(sys.argv[1], sys.argv[2])

if __name__ == "__main__":
    main()


# SCons/Tool/sgiar.py

"""SCons.Tool.sgiar

Tool-specific initialization for SGI ar (library archive).  If CC
exists, static libraries should be built with it, so the prelinker has
a chance to resolve C++ template instantiations.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import SCons.Defaults
import SCons.Tool
import SCons.Util

def generate(env):
    """Add Builders and construction variables for ar to an Environment."""
    SCons.Tool.createStaticLibBuilder(env)
    
    if env.Detect('CC'):
        env['AR']          = 'CC'
        env['ARFLAGS']     = SCons.Util.CLVar('-ar')
        env['ARCOM']       = '$AR $ARFLAGS -o $TARGET $SOURCES'
    else:
        env['AR']          = 'ar'
        env['ARFLAGS']     = SCons.Util.CLVar('r')
        env['ARCOM']       = '$AR $ARFLAGS $TARGET $SOURCES'
        
    env['SHLINK']      = '$LINK'
    env['SHLINKFLAGS'] = SCons.Util.CLVar('$LINKFLAGS -shared')
    env['SHLINKCOM']   = '$SHLINK $SHLINKFLAGS -o $TARGET $SOURCES $_LIBDIRFLAGS $_LIBFLAGS'
    env['LIBPREFIX']   = 'lib'
    env['LIBSUFFIX']   = '.a'

def exists(env):
    return env.Detect('CC') or env.Detect('ar')

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/sgic++.py

"""SCons.Tool.sgic++

Tool-specific initialization for MIPSpro C++ on SGI.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

#forward proxy to the preffered cxx version
from SCons.Tool.sgicxx import *

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/sgicc.py

"""SCons.Tool.sgicc

Tool-specific initialization for MIPSPro cc on SGI.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

from . import cc

def generate(env):
    """Add Builders and construction variables for gcc to an Environment."""
    cc.generate(env)

    env['CXX']        = 'CC'
    env['SHOBJSUFFIX'] = '.o'
    env['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 1

def exists(env):
    return env.Detect('cc')

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/sgicxx.py

"""SCons.Tool.sgic++

Tool-specific initialization for MIPSpro C++ on SGI.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import SCons.Util

import SCons.Tool.cxx
cplusplus = SCons.Tool.cxx
#cplusplus = __import__('cxx', globals(), locals(), [])


def generate(env):
    """Add Builders and construction variables for SGI MIPS C++ to an Environment."""

    cplusplus.generate(env)

    env['CXX']         = 'CC'
    env['CXXFLAGS']    = SCons.Util.CLVar('-LANG:std')
    env['SHCXX']       = '$CXX'
    env['SHOBJSUFFIX'] = '.o'
    env['STATIC_AND_SHARED_OBJECTS_ARE_THE_SAME'] = 1
    
def exists(env):
    return env.Detect('CC')

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/sgilink.py

"""SCons.Tool.sgilink

Tool-specific initialization for the SGI MIPSPro linker on SGI.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#


import SCons.Util

from . import link

linkers = ['CC', 'cc']

def generate(env):
    """Add Builders and construction variables for MIPSPro to an Environment."""
    link.generate(env)
    
    env['LINK'] = env.Detect(linkers) or 'cc'
    env['SHLINKFLAGS'] = SCons.Util.CLVar('$LINKFLAGS -shared')

    # __RPATH is set to $_RPATH in the platform specification if that
    # platform supports it.
    env['RPATHPREFIX'] = '-rpath '
    env['RPATHSUFFIX'] = ''
    env['_RPATH'] = '${_concat(RPATHPREFIX, RPATH, RPATHSUFFIX, __env__)}'

def exists(env):
    return env.Detect(linkers)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/sunar.py

"""SCons.Tool.sunar

Tool-specific initialization for Solaris (Forte) ar (library archive). If CC
exists, static libraries should be built with it, so that template
instantiations can be resolved.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import SCons.Defaults
import SCons.Tool
import SCons.Util

def generate(env):
    """Add Builders and construction variables for ar to an Environment."""
    SCons.Tool.createStaticLibBuilder(env)
    
    if env.Detect('CC'):
        env['AR']          = 'CC'
        env['ARFLAGS']     = SCons.Util.CLVar('-xar')
        env['ARCOM']       = '$AR $ARFLAGS -o $TARGET $SOURCES'
    else:
        env['AR']          = 'ar'
        env['ARFLAGS']     = SCons.Util.CLVar('r')
        env['ARCOM']       = '$AR $ARFLAGS $TARGET $SOURCES'

    env['LIBPREFIX']   = 'lib'
    env['LIBSUFFIX']   = '.a'

def exists(env):
    return env.Detect('CC') or env.Detect('ar')

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/sunc++.py

"""SCons.Tool.sunc++

Tool-specific initialization for C++ on SunOS / Solaris.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"


#forward proxy to the preffered cxx version
from SCons.Tool.suncxx import *


# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/suncc.py

"""SCons.Tool.suncc

Tool-specific initialization for Sun Solaris (Forte) CC and cc.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import SCons.Util

from . import cc

def generate(env):
    """
    Add Builders and construction variables for Forte C and C++ compilers
    to an Environment.
    """
    cc.generate(env)

    env['CXX']          = 'CC'
    env['SHCCFLAGS']    = SCons.Util.CLVar('$CCFLAGS -KPIC')
    env['SHOBJPREFIX']  = 'so_'
    env['SHOBJSUFFIX']  = '.o'

def exists(env):
    return env.Detect('CC')

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/suncxx.py

"""SCons.Tool.sunc++

Tool-specific initialization for C++ on SunOS / Solaris.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import SCons

import os
import re
import subprocess

import SCons.Tool.cxx
cplusplus = SCons.Tool.cxx
# cplusplus = __import__('c++', globals(), locals(), [])

package_info = {}


def get_package_info(package_name, pkginfo, pkgchk):
    try:
        return package_info[package_name]
    except KeyError:
        version = None
        pathname = None
        from subprocess import DEVNULL 

        try:
            with open('/var/sadm/install/contents', 'r', encoding='UTF-8') as f:
                sadm_contents = f.read()
        except EnvironmentError:
            pass
        else:
            sadm_re = re.compile(r'^(\S*/bin/CC)(=\S*)? %s$' % package_name, re.M)
            sadm_match = sadm_re.search(sadm_contents)
            if sadm_match:
                pathname = os.path.dirname(sadm_match.group(1))

        try:
            p = subprocess.Popen([pkginfo, '-l', package_name],
                                 universal_newlines=True,
                                 stdout=subprocess.PIPE,
                                 stderr=DEVNULL)
        except EnvironmentError:
            pass
        else:
            pkginfo_contents = p.communicate()[0]
            version_re = re.compile(r'^ *VERSION:\s*(.*)$', re.M)
            version_match = version_re.search(pkginfo_contents)
            if version_match:
                version = version_match.group(1)

        if pathname is None:
            try:
                p = subprocess.Popen([pkgchk, '-l', package_name],
                                     universal_newlines=True,
                                     stdout=subprocess.PIPE,
                                     stderr=DEVNULL)
            except EnvironmentError:
                pass
            else:
                pkgchk_contents = p.communicate()[0]
                pathname_re = re.compile(r'^Pathname:\s*(.*/bin/CC)$', re.M)
                pathname_match = pathname_re.search(pkgchk_contents)
                if pathname_match:
                    pathname = os.path.dirname(pathname_match.group(1))

        package_info[package_name] = (pathname, version)
        return package_info[package_name]


# use the package installer tool "pkg" to figure out where cppc and what
# version of it is installed
def get_cppc(env):
    cxx = env.subst('$CXX')
    if cxx:
        cppcPath = os.path.dirname(cxx)
    else:
        cppcPath = None

    cppcVersion = None

    pkginfo = env.subst('$PKGINFO')
    pkgchk = env.subst('$PKGCHK')

    for package in ['SPROcpl']:
        path, version = get_package_info(package, pkginfo, pkgchk)
        if path and version:
            cppcPath, cppcVersion = path, version
            break

    return (cppcPath, 'CC', 'CC', cppcVersion)


def generate(env):
    """Add Builders and construction variables for SunPRO C++."""
    path, cxx, shcxx, version = get_cppc(env)
    if path:
        cxx = os.path.join(path, cxx)
        shcxx = os.path.join(path, shcxx)

    cplusplus.generate(env)

    env['CXX'] = cxx
    env['SHCXX'] = shcxx
    env['CXXVERSION'] = version
    env['SHCXXFLAGS'] = SCons.Util.CLVar('$CXXFLAGS -KPIC')
    env['SHOBJPREFIX'] = 'so_'
    env['SHOBJSUFFIX'] = '.o'


def exists(env):
    path, cxx, shcxx, version = get_cppc(env)
    if path and cxx:
        cppc = os.path.join(path, cxx)
        if os.path.exists(cppc):
            return cppc
    return None

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/sunf77.py

"""SCons.Tool.sunf77

Tool-specific initialization for sunf77, the Sun Studio F77 compiler.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import SCons.Util

from .FortranCommon import add_all_to_env

compilers = ['sunf77', 'f77']

def generate(env):
    """Add Builders and construction variables for sunf77 to an Environment."""
    add_all_to_env(env)

    fcomp = env.Detect(compilers) or 'f77'
    env['FORTRAN']  = fcomp
    env['F77']      = fcomp

    env['SHFORTRAN']  = '$FORTRAN'
    env['SHF77']      = '$F77'

    env['SHFORTRANFLAGS'] = SCons.Util.CLVar('$FORTRANFLAGS -KPIC')
    env['SHF77FLAGS'] = SCons.Util.CLVar('$F77FLAGS -KPIC')

def exists(env):
    return env.Detect(compilers)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/sunf90.py

"""SCons.Tool.sunf90

Tool-specific initialization for sunf90, the Sun Studio F90 compiler.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import SCons.Util

from .FortranCommon import add_all_to_env

compilers = ['sunf90', 'f90']

def generate(env):
    """Add Builders and construction variables for sun f90 compiler to an
    Environment."""
    add_all_to_env(env)

    fcomp = env.Detect(compilers) or 'f90'
    env['FORTRAN']  = fcomp
    env['F90']      = fcomp

    env['SHFORTRAN']  = '$FORTRAN'
    env['SHF90']      = '$F90'

    env['SHFORTRANFLAGS'] = SCons.Util.CLVar('$FORTRANFLAGS -KPIC')
    env['SHF90FLAGS'] = SCons.Util.CLVar('$F90FLAGS -KPIC')

def exists(env):
    return env.Detect(compilers)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/sunf95.py

"""SCons.Tool.sunf95

Tool-specific initialization for sunf95, the Sun Studio F95 compiler.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import SCons.Util

from .FortranCommon import add_all_to_env

compilers = ['sunf95', 'f95']

def generate(env):
    """Add Builders and construction variables for sunf95 to an
    Environment."""
    add_all_to_env(env)

    fcomp = env.Detect(compilers) or 'f95'
    env['FORTRAN']  = fcomp
    env['F95']      = fcomp

    env['SHFORTRAN']  = '$FORTRAN'
    env['SHF95']      = '$F95'

    env['SHFORTRANFLAGS'] = SCons.Util.CLVar('$FORTRANFLAGS -KPIC')
    env['SHF95FLAGS'] = SCons.Util.CLVar('$F95FLAGS -KPIC')

def exists(env):
    return env.Detect(compilers)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/sunlink.py

"""SCons.Tool.sunlink

Tool-specific initialization for the Sun Solaris (Forte) linker.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""

#
# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

import os.path

import SCons.Util
from . import link

ccLinker = None

# search for the acc compiler and linker front end

try:
    dirs = os.listdir('/opt')
except (IOError, OSError):
    # Not being able to read the directory because it doesn't exist
    # (IOError) or isn't readable (OSError) is okay.
    dirs = []

for d in dirs:
    linker = '/opt/' + d + '/bin/CC'
    if os.path.exists(linker):
        ccLinker = linker
        break


def generate(env):
    """Add Builders and construction variables for Forte to an Environment."""
    link.generate(env)

    env['SHLINKFLAGS'] = SCons.Util.CLVar('$LINKFLAGS -G')

    env['RPATHPREFIX'] = '-R'
    env['RPATHSUFFIX'] = ''
    env['_RPATH'] = '${_concat(RPATHPREFIX, RPATH, RPATHSUFFIX, __env__)}'

    env['_SHLIBVERSIONFLAGS'] = '$SHLIBVERSIONFLAGS -h $_SHLIBSONAME'
    env['_LDMODULEVERSIONFLAGS'] = '$LDMODULEVERSIONFLAGS -h $_LDMODULESONAME'


def exists(env):
    return ccLinker

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/swig.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""Tool-specific initialization for swig.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""

import os.path
import re
import subprocess
import sys

import SCons.Action
import SCons.Defaults
import SCons.Node
import SCons.Tool
import SCons.Util
import SCons.Warnings

verbose = False

swigs = [ 'swig', 'swig3.0', 'swig2.0' ]

SwigAction = SCons.Action.Action('$SWIGCOM', '$SWIGCOMSTR')

def swigSuffixEmitter(env, source):
    if '-c++' in SCons.Util.CLVar(env.subst("$SWIGFLAGS", source=source)):
        return '$SWIGCXXFILESUFFIX'
    else:
        return '$SWIGCFILESUFFIX'

# Match '%module test', as well as '%module(directors="1") test'
# Also allow for test to be quoted (SWIG permits double quotes, but not single)
# Also allow for the line to have spaces after test if not quoted
_reModule = re.compile(r'%module(\s*\(.*\))?\s+("?)(\S+)\2')

def _find_modules(src):
    """Find all modules referenced by %module lines in `src`, a SWIG .i file.
       Returns a list of all modules, and a flag set if SWIG directors have
       been requested (SWIG will generate an additional header file in this
       case.)"""
    directors = 0
    mnames = []
    try:
        with open(src) as f:
            data = f.read()
        matches = _reModule.findall(data)
    except IOError:
        # If the file's not yet generated, guess the module name from the file stem
        matches = []
        mnames.append(os.path.splitext(os.path.basename(src))[0])

    for m in matches:
        mnames.append(m[2])
        directors = directors or 'directors' in m[0]
    return mnames, directors

def _add_director_header_targets(target, env):
    # Directors only work with C++ code, not C
    suffix = env.subst(env['SWIGCXXFILESUFFIX'])
    # For each file ending in SWIGCXXFILESUFFIX, add a new target director
    # header by replacing the ending with SWIGDIRECTORSUFFIX.
    for x in target[:]:
        n = x.name
        d = x.dir
        if n[-len(suffix):] == suffix:
            target.append(d.File(n[:-len(suffix)] + env['SWIGDIRECTORSUFFIX']))

def _swigEmitter(target, source, env):
    swigflags = env.subst("$SWIGFLAGS", target=target, source=source)
    flags = SCons.Util.CLVar(swigflags)
    for src in source:
        src = str(src.rfile())
        mnames = None
        if "-python" in flags and "-noproxy" not in flags:
            if mnames is None:
                mnames, directors = _find_modules(src)
            if directors:
                _add_director_header_targets(target, env)
            python_files = [m + ".py" for m in mnames]
            outdir = env.subst('$SWIGOUTDIR', target=target, source=source)
            # .py files should be generated in SWIGOUTDIR if specified,
            # otherwise in the same directory as the target
            if outdir:
                python_files = [env.fs.File(os.path.join(outdir, j)) for j in python_files]
            else:
                python_files = [target[0].dir.File(m) for m in python_files]
            target.extend(python_files)
        if "-java" in flags:
            if mnames is None:
                mnames, directors = _find_modules(src)
            if directors:
                _add_director_header_targets(target, env)
            java_files = [[m + ".java", m + "JNI.java"] for m in mnames]
            java_files = SCons.Util.flatten(java_files)
            outdir = env.subst('$SWIGOUTDIR', target=target, source=source)
            if outdir:
                 java_files = [os.path.join(outdir, j) for j in java_files]
            java_files = list(map(env.fs.File, java_files))
            def t_from_s(t, p, s, x):
                return t.dir
            tsm = SCons.Node._target_from_source_map
            tkey = len(tsm)
            tsm[tkey] = t_from_s
            for jf in java_files:
                jf._func_target_from_source = tkey
            target.extend(java_files)
    return (target, source)

def _get_swig_version(env, swig):
    """Run the SWIG command line tool to get and return the version number"""
    version = None
    swig = env.subst(swig)
    if not swig:
        return version
    pipe = SCons.Action._subproc(env, SCons.Util.CLVar(swig) + ['-version'],
                                 stdin = 'devnull',
                                 stderr = 'devnull',
                                 stdout = subprocess.PIPE)
    if pipe.wait() != 0:
        return version

    # MAYBE:   out = SCons.Util.to_str (pipe.stdout.read())
    with pipe.stdout:
        out = SCons.Util.to_str(pipe.stdout.read())

    match = re.search(r'SWIG Version\s+(\S+).*', out, re.MULTILINE)
    if match:
        version = match.group(1)
        if verbose:
            print("Version is: %s" % version)
    else:
        if verbose:
            print("Unable to detect version: [%s]" % out)

    return version

def generate(env):
    """Add Builders and construction variables for swig to an Environment."""
    c_file, cxx_file = SCons.Tool.createCFileBuilders(env)

    c_file.suffix['.i'] = swigSuffixEmitter
    cxx_file.suffix['.i'] = swigSuffixEmitter

    c_file.add_action('.i', SwigAction)
    c_file.add_emitter('.i', _swigEmitter)
    cxx_file.add_action('.i', SwigAction)
    cxx_file.add_emitter('.i', _swigEmitter)

    java_file = SCons.Tool.CreateJavaFileBuilder(env)

    java_file.suffix['.i'] = swigSuffixEmitter

    java_file.add_action('.i', SwigAction)
    java_file.add_emitter('.i', _swigEmitter)

    from SCons.Platform.mingw import MINGW_DEFAULT_PATHS
    from SCons.Platform.cygwin import CYGWIN_DEFAULT_PATHS
    from SCons.Platform.win32 import CHOCO_DEFAULT_PATH

    if sys.platform == 'win32':
        swig = SCons.Tool.find_program_path(env, 'swig', default_paths=MINGW_DEFAULT_PATHS + CYGWIN_DEFAULT_PATHS + CHOCO_DEFAULT_PATH)
        if swig:
            swig_bin_dir = os.path.dirname(swig)
            env.AppendENVPath('PATH', swig_bin_dir)
        else:
            SCons.Warnings.warn(
                SCons.Warnings.SConsWarning,
                'swig tool requested, but binary not found in ENV PATH'
            )

    if 'SWIG' not in env:
        env['SWIG'] = env.Detect(swigs) or swigs[0]

    env['SWIGVERSION'] = _get_swig_version(env, env['SWIG'])
    env['SWIGFLAGS'] = SCons.Util.CLVar('')
    env['SWIGDIRECTORSUFFIX'] = '_wrap.h'
    env['SWIGCFILESUFFIX'] = '_wrap$CFILESUFFIX'
    env['SWIGCXXFILESUFFIX'] = '_wrap$CXXFILESUFFIX'
    env['_SWIGOUTDIR'] = r'${"-outdir \"%s\"" % SWIGOUTDIR}'
    env['SWIGPATH'] = []
    env['SWIGINCPREFIX'] = '-I'
    env['SWIGINCSUFFIX'] = ''
    env['_SWIGINCFLAGS'] = '${_concat(SWIGINCPREFIX, SWIGPATH, SWIGINCSUFFIX,' \
                            '__env__, RDirs, TARGET, SOURCE, affect_signature=False)}'
    env['SWIGCOM'] = '$SWIG -o $TARGET ${_SWIGOUTDIR} ${_SWIGINCFLAGS} $SWIGFLAGS $SOURCES'

def exists(env):
    swig = env.get('SWIG') or env.Detect(['swig'])
    return swig

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/tar.py

"""SCons.Tool.tar

Tool-specific initialization for tar.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import SCons.Action
import SCons.Builder
import SCons.Defaults
import SCons.Node.FS
import SCons.Util

tars = ['tar', 'gtar']

TarAction = SCons.Action.Action('$TARCOM', '$TARCOMSTR')

TarBuilder = SCons.Builder.Builder(action = TarAction,
                                   source_factory = SCons.Node.FS.Entry,
                                   source_scanner = SCons.Defaults.DirScanner,
                                   suffix = '$TARSUFFIX',
                                   multi = 1)


def generate(env):
    """Add Builders and construction variables for tar to an Environment."""
    try:
        bld = env['BUILDERS']['Tar']
    except KeyError:
        bld = TarBuilder
        env['BUILDERS']['Tar'] = bld

    env['TAR']        = env.Detect(tars) or 'gtar'
    env['TARFLAGS']   = SCons.Util.CLVar('-c')
    env['TARCOM']     = '$TAR $TARFLAGS -f $TARGET $SOURCES'
    env['TARSUFFIX']  = '.tar'

def exists(env):
    return env.Detect(tars)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/tex.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

""" Tool-specific initialization for TeX.

Generates .dvi files from .tex files

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""

import os.path
import re
import shutil
import sys
import platform
import glob

import SCons.Action
import SCons.Node
import SCons.Node.FS
import SCons.Util
import SCons.Scanner.LaTeX

Verbose = False

must_rerun_latex = True

# these are files that just need to be checked for changes and then rerun latex
check_suffixes = ['.toc', '.lof', '.lot', '.out', '.nav', '.snm']

# these are files that require bibtex or makeindex to be run when they change
all_suffixes = check_suffixes + ['.bbl', '.idx', '.nlo', '.glo', '.acn', '.bcf']

#
# regular expressions used to search for Latex features
# or outputs that require rerunning latex
#
# search for all .aux files opened by latex (recorded in the .fls file)
openout_aux_re = re.compile(r"OUTPUT *(.*\.aux)")

# search for all .bcf files opened by latex (recorded in the .fls file)
# for use by biber
openout_bcf_re = re.compile(r"OUTPUT *(.*\.bcf)")

#printindex_re = re.compile(r"^[^%]*\\printindex", re.MULTILINE)
#printnomenclature_re = re.compile(r"^[^%]*\\printnomenclature", re.MULTILINE)
#printglossary_re = re.compile(r"^[^%]*\\printglossary", re.MULTILINE)

# search to find rerun warnings
warning_rerun_str = r'(^LaTeX Warning:.*Rerun)|(^Package \w+ Warning:.*Rerun)'
warning_rerun_re = re.compile(warning_rerun_str, re.MULTILINE)

# search to find citation rerun warnings
rerun_citations_str = r"^LaTeX Warning:.*\n.*Rerun to get citations correct"
rerun_citations_re = re.compile(rerun_citations_str, re.MULTILINE)

# search to find undefined references or citations warnings
undefined_references_str = r'(^LaTeX Warning:.*undefined references)|(^Package \w+ Warning:.*undefined citations)'
undefined_references_re = re.compile(undefined_references_str, re.MULTILINE)

# used by the emitter
auxfile_re = re.compile(r".", re.MULTILINE)
tableofcontents_re = re.compile(r"^[^%\n]*\\tableofcontents", re.MULTILINE)
makeindex_re = re.compile(r"^[^%\n]*\\makeindex", re.MULTILINE)
bibliography_re = re.compile(r"^[^%\n]*\\bibliography", re.MULTILINE)
bibunit_re = re.compile(r"^[^%\n]*\\begin\{bibunit\}", re.MULTILINE)
multibib_re = re.compile(r"^[^%\n]*\\newcites\{([^\}]*)\}", re.MULTILINE)
addbibresource_re = re.compile(r"^[^%\n]*\\(addbibresource|addglobalbib|addsectionbib)", re.MULTILINE)
listoffigures_re = re.compile(r"^[^%\n]*\\listoffigures", re.MULTILINE)
listoftables_re = re.compile(r"^[^%\n]*\\listoftables", re.MULTILINE)
hyperref_re = re.compile(r"^[^%\n]*\\usepackage.*\{hyperref\}", re.MULTILINE)
makenomenclature_re = re.compile(r"^[^%\n]*\\makenomenclature", re.MULTILINE)
makeglossary_re = re.compile(r"^[^%\n]*\\makeglossary", re.MULTILINE)
makeglossaries_re = re.compile(r"^[^%\n]*\\makeglossaries", re.MULTILINE)
makeacronyms_re = re.compile(r"^[^%\n]*\\makeglossaries", re.MULTILINE)
beamer_re = re.compile(r"^[^%\n]*\\documentclass\{beamer\}", re.MULTILINE)
regex = r'^[^%\n]*\\newglossary\s*\[([^\]]+)\]?\s*\{([^}]*)\}\s*\{([^}]*)\}\s*\{([^}]*)\}\s*\{([^}]*)\}'
newglossary_re = re.compile(regex, re.MULTILINE)
biblatex_re = re.compile(r"^[^%\n]*\\usepackage.*\{biblatex\}", re.MULTILINE)

newglossary_suffix = []

# search to find all files included by Latex
include_re = re.compile(r'^[^%\n]*\\(?:include|input){([^}]*)}', re.MULTILINE)
includeOnly_re = re.compile(r'^[^%\n]*\\(?:include){([^}]*)}', re.MULTILINE)

# search to find all graphics files included by Latex
includegraphics_re = re.compile(r'^[^%\n]*\\(?:includegraphics(?:\[[^\]]+\])?){([^}]*)}', re.MULTILINE)

# search to find all files opened by Latex (recorded in .log file)
openout_re = re.compile(r"OUTPUT *(.*)")

# list of graphics file extensions for TeX and LaTeX
TexGraphics   = SCons.Scanner.LaTeX.TexGraphics
LatexGraphics = SCons.Scanner.LaTeX.LatexGraphics

# An Action sufficient to build any generic tex file.
TeXAction = None

# An action to build a latex file.  This action might be needed more
# than once if we are dealing with labels and bibtex.
LaTeXAction = None

# An action to run BibTeX on a file.
BibTeXAction = None

# An action to run Biber on a file.
BiberAction = None

# An action to run MakeIndex on a file.
MakeIndexAction = None

# An action to run MakeIndex (for nomencl) on a file.
MakeNclAction = None

# An action to run MakeIndex (for glossary) on a file.
MakeGlossaryAction = None

# An action to run MakeIndex (for acronyms) on a file.
MakeAcronymsAction = None

# An action to run MakeIndex (for newglossary commands) on a file.
MakeNewGlossaryAction = None

# Used as a return value of modify_env_var if the variable is not set.
_null = SCons.Scanner.LaTeX._null

modify_env_var = SCons.Scanner.LaTeX.modify_env_var

def check_file_error_message(utility, filename='log'):
    msg = '%s returned an error, check the %s file\n' % (utility, filename)
    sys.stdout.write(msg)

def FindFile(name,suffixes,paths,env,requireExt=False):
    if requireExt:
        name,ext = SCons.Util.splitext(name)
        # if the user gave an extension use it.
        if ext:
            name = name + ext
    if Verbose:
        print(" searching for '%s' with extensions: " % name,suffixes)

    for path in paths:
        testName = os.path.join(path,name)
        if Verbose:
            print(" look for '%s'" % testName)
        if os.path.isfile(testName):
            if Verbose:
                print(" found '%s'" % testName)
            return env.fs.File(testName)
        else:
            name_ext = SCons.Util.splitext(testName)[1]
            if name_ext:
                continue

            # if no suffix try adding those passed in
            for suffix in suffixes:
                testNameExt = testName + suffix
                if Verbose:
                    print(" look for '%s'" % testNameExt)

                if os.path.isfile(testNameExt):
                    if Verbose:
                        print(" found '%s'" % testNameExt)
                    return env.fs.File(testNameExt)
    if Verbose:
        print(" did not find '%s'" % name)
    return None

def InternalLaTeXAuxAction(XXXLaTeXAction, target = None, source= None, env=None):
    """A builder for LaTeX files that checks the output in the aux file
    and decides how many times to use LaTeXAction, and BibTeXAction."""

    global must_rerun_latex

    # This routine is called with two actions. In this file for DVI builds
    # with LaTeXAction and from the pdflatex.py with PDFLaTeXAction
    # set this up now for the case where the user requests a different extension
    # for the target filename
    if XXXLaTeXAction == LaTeXAction:
       callerSuffix = ".dvi"
    else:
       callerSuffix = env['PDFSUFFIX']

    basename = SCons.Util.splitext(str(source[0]))[0]
    basedir = os.path.split(str(source[0]))[0]
    basefile = os.path.split(str(basename))[1]
    abspath = os.path.abspath(basedir)

    targetext = os.path.splitext(str(target[0]))[1]
    targetdir = os.path.split(str(target[0]))[0]

    saved_env = {}
    for var in SCons.Scanner.LaTeX.LaTeX.env_variables:
        saved_env[var] = modify_env_var(env, var, abspath)

    # Create base file names with the target directory since the auxiliary files
    # will be made there.   That's because the *COM variables have the cd
    # command in the prolog. We check
    # for the existence of files before opening them--even ones like the
    # aux file that TeX always creates--to make it possible to write tests
    # with stubs that don't necessarily generate all of the same files.

    targetbase = os.path.join(targetdir, basefile)

    # if there is a \makeindex there will be a .idx and thus
    # we have to run makeindex at least once to keep the build
    # happy even if there is no index.
    # Same for glossaries, nomenclature, and acronyms
    src_content = source[0].get_text_contents()
    run_makeindex = makeindex_re.search(src_content) and not os.path.isfile(targetbase + '.idx')
    run_nomenclature = makenomenclature_re.search(src_content) and not os.path.isfile(targetbase + '.nlo')
    run_glossary = makeglossary_re.search(src_content) and not os.path.isfile(targetbase + '.glo')
    run_glossaries = makeglossaries_re.search(src_content) and not os.path.isfile(targetbase + '.glo')
    run_acronyms = makeacronyms_re.search(src_content) and not os.path.isfile(targetbase + '.acn')

    saved_hashes = {}
    suffix_nodes = {}


    for suffix in all_suffixes+sum(newglossary_suffix, []):
        theNode = env.fs.File(targetbase + suffix)
        suffix_nodes[suffix] = theNode
        saved_hashes[suffix] = theNode.get_csig()

    if Verbose:
        print("hashes: ",saved_hashes)

    must_rerun_latex = True

    # .aux files already processed by BibTex
    already_bibtexed = []

    #
    # routine to update MD5 hash and compare
    #
    def check_MD5(filenode, suffix):
        global must_rerun_latex
        # two calls to clear old csig
        filenode.clear_memoized_values()
        filenode.ninfo = filenode.new_ninfo()
        new_md5 = filenode.get_csig()

        if saved_hashes[suffix] == new_md5:
            if Verbose:
                print("file %s not changed" % (targetbase+suffix))
            return False        # unchanged
        saved_hashes[suffix] = new_md5
        must_rerun_latex = True
        if Verbose:
            print("file %s changed, rerunning Latex, new hash = " % (targetbase+suffix), new_md5)
        return True     # changed

    # generate the file name that latex will generate
    resultfilename = targetbase + callerSuffix

    count = 0

    while must_rerun_latex and count < int(env.subst('$LATEXRETRIES')):
        result = XXXLaTeXAction(target, source, env)
        if result != 0:
            return result

        count = count + 1

        must_rerun_latex = False
        # Decide if various things need to be run, or run again.

        # Read the log file to find warnings/errors
        logfilename = targetbase + '.log'
        logContent = ''
        if os.path.isfile(logfilename):
            with open(logfilename, "rb") as f:
                logContent = f.read().decode(errors='replace')


        # Read the fls file to find all .aux files
        flsfilename = targetbase + '.fls'
        flsContent = ''
        auxfiles = []
        if os.path.isfile(flsfilename):
            with open(flsfilename, "r") as f:
                flsContent = f.read()
            auxfiles = openout_aux_re.findall(flsContent)
            # remove duplicates
            dups = {}
            for x in auxfiles:
                dups[x] = 1
            auxfiles = list(dups.keys())

        bcffiles = []
        if os.path.isfile(flsfilename):
            with open(flsfilename, "r") as f:
                flsContent = f.read()
            bcffiles = openout_bcf_re.findall(flsContent)
            # remove duplicates
            dups = {}
            for x in bcffiles:
                dups[x] = 1
            bcffiles = list(dups.keys())

        if Verbose:
            print("auxfiles ",auxfiles)
            print("bcffiles ",bcffiles)

        # Now decide if bibtex will need to be run.
        # The information that bibtex reads from the .aux file is
        # pass-independent. If we find (below) that the .bbl file is unchanged,
        # then the last latex saw a correct bibliography.
        # Therefore only do this once
        # Go through all .aux files and remember the files already done.
        for auxfilename in auxfiles:
            if auxfilename not in already_bibtexed:
                already_bibtexed.append(auxfilename)
                target_aux = os.path.join(targetdir, auxfilename)
                if os.path.isfile(target_aux):
                    with open(target_aux, "r") as f:
                        content = f.read()
                    if content.find("bibdata") != -1:
                        if Verbose:
                            print("Need to run bibtex on ",auxfilename)
                        bibfile = env.fs.File(SCons.Util.splitext(target_aux)[0])
                        result = BibTeXAction(bibfile, bibfile, env)
                        if result != 0:
                            check_file_error_message(env['BIBTEX'], 'blg')
                        must_rerun_latex = True

        # Now decide if biber will need to be run.
        # When the backend for biblatex is biber (by choice or default) the
        # citation information is put in the .bcf file.
        # The information that biber reads from the .bcf file is
        # pass-independent. If we find (below) that the .bbl file is unchanged,
        # then the last latex saw a correct bibliography.
        # Therefore only do this once
        # Go through all .bcf files and remember the files already done.
        for bcffilename in bcffiles:
            if bcffilename not in already_bibtexed:
                already_bibtexed.append(bcffilename)
                target_bcf = os.path.join(targetdir, bcffilename)
                if os.path.isfile(target_bcf):
                    with open(target_bcf, "r") as f:
                        content = f.read()
                    if content.find("bibdata") != -1:
                        if Verbose:
                            print("Need to run biber on ",bcffilename)
                        bibfile = env.fs.File(SCons.Util.splitext(target_bcf)[0])
                        result = BiberAction(bibfile, bibfile, env)
                        if result != 0:
                            check_file_error_message(env['BIBER'], 'blg')
                        must_rerun_latex = True

        # Now decide if latex will need to be run again due to index.
        if check_MD5(suffix_nodes['.idx'],'.idx') or (count == 1 and run_makeindex):
            # We must run makeindex
            if Verbose:
                print("Need to run makeindex")
            idxfile = suffix_nodes['.idx']
            result = MakeIndexAction(idxfile, idxfile, env)
            if result != 0:
                check_file_error_message(env['MAKEINDEX'], 'ilg')
                return result

        # TO-DO: need to add a way for the user to extend this list for whatever
        # auxiliary files they create in other (or their own) packages
        # Harder is case is where an action needs to be called -- that should be rare (I hope?)

        for index in check_suffixes:
            check_MD5(suffix_nodes[index],index)

        # Now decide if latex will need to be run again due to nomenclature.
        if check_MD5(suffix_nodes['.nlo'],'.nlo') or (count == 1 and run_nomenclature):
            # We must run makeindex
            if Verbose:
                print("Need to run makeindex for nomenclature")
            nclfile = suffix_nodes['.nlo']
            result = MakeNclAction(nclfile, nclfile, env)
            if result != 0:
                check_file_error_message('%s (nomenclature)' % env['MAKENCL'],
                                         'nlg')
                #return result

        # Now decide if latex will need to be run again due to glossary.
        if check_MD5(suffix_nodes['.glo'],'.glo') or (count == 1 and run_glossaries) or (count == 1 and run_glossary):
            # We must run makeindex
            if Verbose:
                print("Need to run makeindex for glossary")
            glofile = suffix_nodes['.glo']
            result = MakeGlossaryAction(glofile, glofile, env)
            if result != 0:
                check_file_error_message('%s (glossary)' % env['MAKEGLOSSARY'],
                                         'glg')
                #return result

        # Now decide if latex will need to be run again due to acronyms.
        if check_MD5(suffix_nodes['.acn'],'.acn') or (count == 1 and run_acronyms):
            # We must run makeindex
            if Verbose:
                print("Need to run makeindex for acronyms")
            acrfile = suffix_nodes['.acn']
            result = MakeAcronymsAction(acrfile, acrfile, env)
            if result != 0:
                check_file_error_message('%s (acronyms)' % env['MAKEACRONYMS'],
                                         'alg')
                return result

        # Now decide if latex will need to be run again due to newglossary command.
        for ng in newglossary_suffix:
            if check_MD5(suffix_nodes[ng[2]], ng[2]) or (count == 1):
                # We must run makeindex
                if Verbose:
                    print("Need to run makeindex for newglossary")
                newglfile = suffix_nodes[ng[2]]
                MakeNewGlossaryAction = SCons.Action.Action(
                    "$MAKENEWGLOSSARYCOM ${SOURCE.filebase}%s -s ${SOURCE.filebase}.ist -t ${SOURCE.filebase}%s -o ${SOURCE.filebase}%s"
                    % (ng[2], ng[0], ng[1]),
                    "$MAKENEWGLOSSARYCOMSTR",
                )

                result = MakeNewGlossaryAction(newglfile, newglfile, env)
                if result != 0:
                    check_file_error_message(
                        '%s (newglossary)' % env['MAKENEWGLOSSARY'], ng[0]
                    )
                    return result

        # Now decide if latex needs to be run yet again to resolve warnings.
        if warning_rerun_re.search(logContent):
            must_rerun_latex = True
            if Verbose:
                print("rerun Latex due to latex or package rerun warning")

        if rerun_citations_re.search(logContent):
            must_rerun_latex = True
            if Verbose:
                print("rerun Latex due to 'Rerun to get citations correct' warning")

        if undefined_references_re.search(logContent):
            must_rerun_latex = True
            if Verbose:
                print("rerun Latex due to undefined references or citations")

        if count >= int(env.subst('$LATEXRETRIES')) and must_rerun_latex:
            print("reached max number of retries on Latex ,",int(env.subst('$LATEXRETRIES')))
# end of while loop

    # rename Latex's output to what the target name is
    if not (str(target[0]) == resultfilename  and  os.path.isfile(resultfilename)):
        if os.path.isfile(resultfilename):
            print("move %s to %s" % (resultfilename, str(target[0]), ))
            shutil.move(resultfilename,str(target[0]))

    # Original comment (when TEXPICTS was not restored):
    # The TEXPICTS environment variable is needed by a dvi -> pdf step
    # later on Mac OSX so leave it
    #
    # It is also used when searching for pictures (implicit dependencies).
    # Why not set the variable again in the respective builder instead
    # of leaving local modifications in the environment? What if multiple
    # latex builds in different directories need different TEXPICTS?
    for var in SCons.Scanner.LaTeX.LaTeX.env_variables:
        if var == 'TEXPICTS':
            continue
        if saved_env[var] is _null:
            try:
                del env['ENV'][var]
            except KeyError:
                pass # was never set
        else:
            env['ENV'][var] = saved_env[var]

    return result

def LaTeXAuxAction(target=None, source=None, env=None):
    result = InternalLaTeXAuxAction(LaTeXAction, target, source, env)
    return result


LaTeX_re = re.compile("\\\\document(style|class)")


def is_LaTeX(flist, env, abspath) -> bool:
    """Scan a file list to decide if it's TeX- or LaTeX-flavored."""

    # We need to scan files that are included in case the
    # \documentclass command is in them.

    # get path list from both env['TEXINPUTS'] and env['ENV']['TEXINPUTS']
    savedpath = modify_env_var(env, "TEXINPUTS", abspath)
    paths = env["ENV"]["TEXINPUTS"]
    if SCons.Util.is_List(paths):
        pass
    else:
        # Split at os.pathsep to convert into absolute path
        paths = paths.split(os.pathsep)

    # now that we have the path list restore the env
    if savedpath is _null:
        try:
            del env["ENV"]["TEXINPUTS"]
        except KeyError:
            pass  # was never set
    else:
        env["ENV"]["TEXINPUTS"] = savedpath
    if Verbose:
        print("is_LaTeX search path ", paths)
        print("files to search: ", flist)

    # Now that we have the search path and file list, check each one
    file_test = False
    for f in flist:
        if Verbose:
            print(f" checking for Latex source {f}")

        content = f.get_text_contents()
        if LaTeX_re.search(content):
            if Verbose:
                print(f"file {f} is a LaTeX file")
            return True
        if Verbose:
            print(f"file {f} is not a LaTeX file")

        # now find included files
        inc_files = []
        inc_files.extend(include_re.findall(content))
        if Verbose:
            print(f"files included by '{f}': ", inc_files)
        # inc_files is list of file names as given. need to find them
        # using TEXINPUTS paths.

        # search the included files
        for src in inc_files:
            srcNode = FindFile(
                src, [".tex", ".ltx", ".latex"], paths, env, requireExt=False
            )
            # make this a list since is_LaTeX takes a list.
            fileList = [srcNode]
            if Verbose:
                print("FindFile found ", srcNode)
            if srcNode is not None:
                file_test = is_LaTeX(fileList, env, abspath)

            # return on first file that finds latex is needed.
            if file_test:
                return True

        if Verbose:
            print(f" done scanning {f}")

    return False


def TeXLaTeXFunction(target = None, source= None, env=None):
    """A builder for TeX and LaTeX that scans the source file to
    decide the "flavor" of the source and then executes the appropriate
    program."""

    # find these paths for use in is_LaTeX to search for included files
    basedir = os.path.split(str(source[0]))[0]
    abspath = os.path.abspath(basedir)

    if is_LaTeX(source,env,abspath):
        result = LaTeXAuxAction(target,source,env)
        if result != 0:
            check_file_error_message(env['LATEX'])
    else:
        result = TeXAction(target,source,env)
        if result != 0:
            check_file_error_message(env['TEX'])
    return result

def TeXLaTeXStrFunction(target = None, source= None, env=None):
    """A strfunction for TeX and LaTeX that scans the source file to
    decide the "flavor" of the source and then returns the appropriate
    command string."""
    if env.GetOption("no_exec"):

        # find these paths for use in is_LaTeX to search for included files
        basedir = os.path.split(str(source[0]))[0]
        abspath = os.path.abspath(basedir)

        if is_LaTeX(source,env,abspath):
            result = env.subst('$LATEXCOM',0,target,source)+" ..."
        else:
            result = env.subst("$TEXCOM",0,target,source)+" ..."
    else:
        result = ''
    return result

def tex_eps_emitter(target, source, env):
    """An emitter for TeX and LaTeX sources when
    executing tex or latex. It will accept .ps and .eps
    graphics files
    """
    (target, source) = tex_emitter_core(target, source, env, TexGraphics)

    return (target, source)

def tex_pdf_emitter(target, source, env):
    """An emitter for TeX and LaTeX sources when
    executing pdftex or pdflatex. It will accept graphics
    files of types .pdf, .jpg, .png, .gif, and .tif
    """
    (target, source) = tex_emitter_core(target, source, env, LatexGraphics)

    return (target, source)

def ScanFiles(theFile, target, paths, file_tests, file_tests_search, env, graphics_extensions, targetdir, aux_files):
    """ For theFile (a Node) update any file_tests and search for graphics files
    then find all included files and call ScanFiles recursively for each of them"""

    content = theFile.get_text_contents()
    if Verbose:
        print(" scanning ",str(theFile))

    for i in range(len(file_tests_search)):
        if file_tests[i][0] is None:
            if Verbose:
                print("scan i ",i," files_tests[i] ",file_tests[i], file_tests[i][1])
            file_tests[i][0] = file_tests_search[i].search(content)
            if Verbose and file_tests[i][0]:
                print("   found match for ",file_tests[i][1][-1])
            # for newglossary insert the suffixes in file_tests[i]
            if file_tests[i][0] and file_tests[i][1][-1] == 'newglossary':
                findresult = file_tests_search[i].findall(content)
                for l in range(len(findresult)) :
                    (file_tests[i][1]).insert(0,'.'+findresult[l][3])
                    (file_tests[i][1]).insert(0,'.'+findresult[l][2])
                    (file_tests[i][1]).insert(0,'.'+findresult[l][0])
                    suffix_list = ['.'+findresult[l][0],'.'+findresult[l][2],'.'+findresult[l][3] ]
                    newglossary_suffix.append(suffix_list)
                if Verbose:
                    print(" new suffixes for newglossary ",newglossary_suffix)


    incResult = includeOnly_re.search(content)
    if incResult:
        aux_files.append(os.path.join(targetdir, incResult.group(1)))
    if Verbose:
        print(r"\include file names : ", aux_files)
    # recursively call this on each of the included files
    inc_files = [ ]
    inc_files.extend( include_re.findall(content) )
    if Verbose:
        print("files included by '%s': "%str(theFile),inc_files)
    # inc_files is list of file names as given. need to find them
    # using TEXINPUTS paths.

    for src in inc_files:
        srcNode = FindFile(src,['.tex','.ltx','.latex'],paths,env,requireExt=False)
        if srcNode is not None:
            file_tests = ScanFiles(srcNode, target, paths, file_tests, file_tests_search, env, graphics_extensions, targetdir, aux_files)
    if Verbose:
        print(" done scanning ",str(theFile))
    return file_tests

def tex_emitter_core(target, source, env, graphics_extensions):
    """An emitter for TeX and LaTeX sources.
    For LaTeX sources we try and find the common created files that
    are needed on subsequent runs of latex to finish tables of contents,
    bibliographies, indices, lists of figures, and hyperlink references.
    """
    basename = SCons.Util.splitext(str(source[0]))[0]
    basefile = os.path.split(str(basename))[1]
    targetdir = os.path.split(str(target[0]))[0]
    targetbase = os.path.join(targetdir, basefile)

    basedir = os.path.split(str(source[0]))[0]
    abspath = os.path.abspath(basedir)
    target[0].attributes.path = abspath

    #
    # file names we will make use of in searching the sources and log file
    #
    emit_suffixes = ['.aux', '.log', '.ilg', '.blg', '.nls', '.nlg', '.gls', '.glg', '.alg'] + all_suffixes
    auxfilename = targetbase + '.aux'
    logfilename = targetbase + '.log'
    flsfilename = targetbase + '.fls'
    syncfilename = targetbase + '.synctex.gz'

    env.SideEffect(auxfilename,target[0])
    env.SideEffect(logfilename,target[0])
    env.SideEffect(flsfilename,target[0])
    env.SideEffect(syncfilename,target[0])
    if Verbose:
        print("side effect :",auxfilename,logfilename,flsfilename,syncfilename)
    env.Clean(target[0],auxfilename)
    env.Clean(target[0],logfilename)
    env.Clean(target[0],flsfilename)
    env.Clean(target[0],syncfilename)

    content = source[0].get_text_contents()

    # set up list with the regular expressions
    # we use to find features used
    file_tests_search = [auxfile_re,
                         makeindex_re,
                         bibliography_re,
                         bibunit_re,
                         multibib_re,
                         addbibresource_re,
                         tableofcontents_re,
                         listoffigures_re,
                         listoftables_re,
                         hyperref_re,
                         makenomenclature_re,
                         makeglossary_re,
                         makeglossaries_re,
                         makeacronyms_re,
                         beamer_re,
                         newglossary_re,
                         biblatex_re ]
    # set up list with the file suffixes that need emitting
    # when a feature is found
    file_tests_suff = [['.aux','aux_file'],
                  ['.idx', '.ind', '.ilg','makeindex'],
                  ['.bbl', '.blg','bibliography'],
                  ['.bbl', '.blg','bibunit'],
                  ['.bbl', '.blg','multibib'],
                  ['.bbl', '.blg','.bcf','addbibresource'],
                  ['.toc','contents'],
                  ['.lof','figures'],
                  ['.lot','tables'],
                  ['.out','hyperref'],
                  ['.nlo', '.nls', '.nlg','nomenclature'],
                  ['.glo', '.gls', '.glg','glossary'],
                  ['.glo', '.gls', '.glg','glossaries'],
                  ['.acn', '.acr', '.alg','acronyms'],
                  ['.nav', '.snm', '.out', '.toc','beamer'],
                  ['newglossary',],
                  ['.bcf', '.blg','biblatex'] ]
    # for newglossary the suffixes are added as we find the command
    # build the list of lists
    file_tests = []
    for i in range(len(file_tests_search)):
        file_tests.append( [None, file_tests_suff[i]] )

    # TO-DO: need to add a way for the user to extend this list for whatever
    # auxiliary files they create in other (or their own) packages

    # get path list from both env['TEXINPUTS'] and env['ENV']['TEXINPUTS']
    savedpath = modify_env_var(env, 'TEXINPUTS', abspath)
    paths = env['ENV']['TEXINPUTS']
    if SCons.Util.is_List(paths):
        pass
    else:
        # Split at os.pathsep to convert into absolute path
        paths = paths.split(os.pathsep)

    # now that we have the path list restore the env
    if savedpath is _null:
        try:
            del env['ENV']['TEXINPUTS']
        except KeyError:
            pass # was never set
    else:
        env['ENV']['TEXINPUTS'] = savedpath
    if Verbose:
        print("search path ",paths)

    # scan all sources for side effect files
    aux_files = []
    file_tests = ScanFiles(source[0], target, paths, file_tests, file_tests_search, env, graphics_extensions, targetdir, aux_files)

    for (theSearch,suffix_list) in file_tests:
        # add side effects if feature is present.If file is to be generated,add all side effects
        if Verbose and theSearch:
            print("check side effects for ",suffix_list[-1])
        if theSearch is not None or not source[0].exists():
            file_list = [targetbase,]
            # for bibunit we need a list of files
            if suffix_list[-1] == 'bibunit':
                file_basename = os.path.join(targetdir, 'bu*.aux')
                file_list = glob.glob(file_basename)
                # remove the suffix '.aux'
                for fl in file_list.copy():
                    file_list.append(SCons.Util.splitext(fl)[0])
            # for multibib we need a list of files
            if suffix_list[-1] == 'multibib':
                for multibibmatch in multibib_re.finditer(content):
                    if Verbose:
                        print("multibib match ",multibibmatch.group(1))
                    if multibibmatch is not None:
                        baselist = multibibmatch.group(1).split(',')
                        if Verbose:
                            print("multibib list ", baselist)
                        for bl in baselist:
                            file_list.append(os.path.join(targetdir, bl))
            # now define the side effects
            for file_name in file_list:
                for suffix in suffix_list[:-1]:
                    env.SideEffect(file_name + suffix,target[0])
                    if Verbose:
                        print("side effect tst :",file_name + suffix, " target is ",str(target[0]))
                    env.Clean(target[0],file_name + suffix)

    for aFile in aux_files:
        aFile_base = SCons.Util.splitext(aFile)[0]
        env.SideEffect(aFile_base + '.aux',target[0])
        if Verbose:
            print("side effect aux :",aFile_base + '.aux')
        env.Clean(target[0],aFile_base + '.aux')
    # read fls file to get all other files that latex creates and will read on the next pass
    # remove files from list that we explicitly dealt with above
    if os.path.isfile(flsfilename):
        with open(flsfilename, "r") as f:
            content = f.read()
        out_files = openout_re.findall(content)
        myfiles = [auxfilename, logfilename, flsfilename, targetbase+'.dvi',targetbase+'.pdf']
        for filename in out_files[:]:
            if filename in myfiles:
                out_files.remove(filename)
        env.SideEffect(out_files,target[0])
        if Verbose:
            print("side effect fls :",out_files)
        env.Clean(target[0],out_files)

    return (target, source)


TeXLaTeXAction = None

def generate(env):
    """Add Builders and construction variables for TeX to an Environment."""

    global TeXLaTeXAction
    if TeXLaTeXAction is None:
        TeXLaTeXAction = SCons.Action.Action(TeXLaTeXFunction,
                              strfunction=TeXLaTeXStrFunction)

    env.AppendUnique(LATEXSUFFIXES=SCons.Tool.LaTeXSuffixes)

    generate_common(env)

    from . import dvi
    dvi.generate(env)

    bld = env['BUILDERS']['DVI']
    bld.add_action('.tex', TeXLaTeXAction)
    bld.add_emitter('.tex', tex_eps_emitter)

def generate_darwin(env):
    try:
        environ = env['ENV']
    except KeyError:
        environ = {}
        env['ENV'] = environ

    if platform.system() == 'Darwin':
        try:
            ospath = env['ENV']['PATHOSX']
        except KeyError:
            ospath = None
        if ospath:
            env.AppendENVPath('PATH', ospath)

def generate_common(env):
    """Add internal Builders and construction variables for LaTeX to an Environment."""

    # Add OSX system paths so TeX tools can be found
    # when a list of tools is given the exists() method is not called
    generate_darwin(env)

    # A generic tex file Action, sufficient for all tex files.
    global TeXAction
    if TeXAction is None:
        TeXAction = SCons.Action.Action("$TEXCOM", "$TEXCOMSTR")

    # An Action to build a latex file.  This might be needed more
    # than once if we are dealing with labels and bibtex.
    global LaTeXAction
    if LaTeXAction is None:
        LaTeXAction = SCons.Action.Action("$LATEXCOM", "$LATEXCOMSTR")

    # Define an action to run BibTeX on a file.
    global BibTeXAction
    if BibTeXAction is None:
        BibTeXAction = SCons.Action.Action("$BIBTEXCOM", "$BIBTEXCOMSTR")

    # Define an action to run Biber on a file.
    global BiberAction
    if BiberAction is None:
        BiberAction = SCons.Action.Action("$BIBERCOM", "$BIBERCOMSTR")

    # Define an action to run MakeIndex on a file.
    global MakeIndexAction
    if MakeIndexAction is None:
        MakeIndexAction = SCons.Action.Action("$MAKEINDEXCOM", "$MAKEINDEXCOMSTR")

    # Define an action to run MakeIndex on a file for nomenclatures.
    global MakeNclAction
    if MakeNclAction is None:
        MakeNclAction = SCons.Action.Action("$MAKENCLCOM", "$MAKENCLCOMSTR")

    # Define an action to run MakeIndex on a file for glossaries.
    global MakeGlossaryAction
    if MakeGlossaryAction is None:
        MakeGlossaryAction = SCons.Action.Action("$MAKEGLOSSARYCOM", "$MAKEGLOSSARYCOMSTR")

    # Define an action to run MakeIndex on a file for acronyms.
    global MakeAcronymsAction
    if MakeAcronymsAction is None:
        MakeAcronymsAction = SCons.Action.Action("$MAKEACRONYMSCOM", "$MAKEACRONYMSCOMSTR")

    try:
        environ = env['ENV']
    except KeyError:
        environ = {}
        env['ENV'] = environ

    # Some Linux platforms have pdflatex set up in a way
    # that requires that the HOME environment variable be set.
    # Add it here if defined.
    v = os.environ.get('HOME')
    if v:
        environ['HOME'] = v

    CDCOM = 'cd '
    if platform.system() == 'Windows':
        # allow cd command to change drives on Windows
        CDCOM = 'cd /D '

    env['TEX']      = 'tex'
    env['TEXFLAGS'] = SCons.Util.CLVar('-interaction=nonstopmode -recorder')
    env['TEXCOM']   = CDCOM + '${TARGET.dir} && $TEX $TEXFLAGS ${SOURCE.file}'

    env['PDFTEX']      = 'pdftex'
    env['PDFTEXFLAGS'] = SCons.Util.CLVar('-interaction=nonstopmode -recorder')
    env['PDFTEXCOM']   = CDCOM + '${TARGET.dir} && $PDFTEX $PDFTEXFLAGS ${SOURCE.file}'

    env['LATEX']        = 'latex'
    env['LATEXFLAGS']   = SCons.Util.CLVar('-interaction=nonstopmode -recorder')
    env['LATEXCOM']     = CDCOM + '${TARGET.dir} && $LATEX $LATEXFLAGS ${SOURCE.file}'
    env['LATEXRETRIES'] = 4

    env['PDFLATEX']      = 'pdflatex'
    env['PDFLATEXFLAGS'] = SCons.Util.CLVar('-interaction=nonstopmode -recorder')
    env['PDFLATEXCOM']   = CDCOM + '${TARGET.dir} && $PDFLATEX $PDFLATEXFLAGS ${SOURCE.file}'

    env['BIBTEX']      = 'bibtex'
    env['BIBTEXFLAGS'] = SCons.Util.CLVar('')
    env['BIBTEXCOM']   = CDCOM + '${TARGET.dir} && $BIBTEX $BIBTEXFLAGS ${SOURCE.filebase}'

    env['BIBER']      = 'biber'
    env['BIBERFLAGS'] = SCons.Util.CLVar('')
    env['BIBERCOM']   = CDCOM + '${TARGET.dir} && $BIBER $BIBERFLAGS ${SOURCE.filebase}'

    env['MAKEINDEX']      = 'makeindex'
    env['MAKEINDEXFLAGS'] = SCons.Util.CLVar('')
    env['MAKEINDEXCOM']   = CDCOM + '${TARGET.dir} && $MAKEINDEX $MAKEINDEXFLAGS ${SOURCE.file}'

    env['MAKEGLOSSARY']      = 'makeindex'
    env['MAKEGLOSSARYSTYLE'] = '${SOURCE.filebase}.ist'
    env['MAKEGLOSSARYFLAGS'] = SCons.Util.CLVar('-s ${MAKEGLOSSARYSTYLE} -t ${SOURCE.filebase}.glg')
    env['MAKEGLOSSARYCOM']   = CDCOM + '${TARGET.dir} && $MAKEGLOSSARY ${SOURCE.filebase}.glo $MAKEGLOSSARYFLAGS -o ${SOURCE.filebase}.gls'

    env['MAKEACRONYMS']      = 'makeindex'
    env['MAKEACRONYMSSTYLE'] = '${SOURCE.filebase}.ist'
    env['MAKEACRONYMSFLAGS'] = SCons.Util.CLVar('-s ${MAKEACRONYMSSTYLE} -t ${SOURCE.filebase}.alg')
    env['MAKEACRONYMSCOM']   = CDCOM + '${TARGET.dir} && $MAKEACRONYMS ${SOURCE.filebase}.acn $MAKEACRONYMSFLAGS -o ${SOURCE.filebase}.acr'

    env['MAKENCL']      = 'makeindex'
    env['MAKENCLSTYLE'] = 'nomencl.ist'
    env['MAKENCLFLAGS'] = '-s ${MAKENCLSTYLE} -t ${SOURCE.filebase}.nlg'
    env['MAKENCLCOM']   = CDCOM + '${TARGET.dir} && $MAKENCL ${SOURCE.filebase}.nlo $MAKENCLFLAGS -o ${SOURCE.filebase}.nls'

    env['MAKENEWGLOSSARY']      = 'makeindex'
    env['MAKENEWGLOSSARYCOM']   = CDCOM + '${TARGET.dir} && $MAKENEWGLOSSARY '

def exists(env):
    generate_darwin(env)
    return env.Detect('tex')

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/textfile.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""
Textfile/Substfile builder for SCons.

    Create file 'target' which typically is a textfile.  The 'source'
    may be any combination of strings, Nodes, or lists of same.  A
    'linesep' will be put between any part written and defaults to
    os.linesep.

    The only difference between the Textfile builder and the Substfile
    builder is that strings are converted to Value() nodes for the
    former and File() nodes for the latter.  To insert files in the
    former or strings in the latter, wrap them in a File() or Value(),
    respectively.

    The values of SUBST_DICT first have any construction variables
    expanded (its keys are not expanded).  If a value of SUBST_DICT is
    a python callable function, it is called and the result is expanded
    as the value.  Values are substituted in a "random" order; if any
    substitution could be further expanded by another substitution, it
    is unpredictable whether the expansion will occur.
"""

import SCons


from SCons.Node import Node
from SCons.Node.Python import Value
from SCons.Util import is_String, is_Sequence, is_Dict, to_bytes


TEXTFILE_FILE_WRITE_MODE = 'w'

LINESEP = '\n'


def _do_subst(node, subs):
    """
    Fetch the node contents and replace all instances of the keys with
    their values.  For example, if subs is
        {'%VERSION%': '1.2345', '%BASE%': 'MyProg', '%prefix%': '/bin'},
    then all instances of %VERSION% in the file will be replaced with
    1.2345 and so forth.
    """
    contents = node.get_text_contents()
    if subs:
        for (k, val) in subs:
            contents = contents.replace(k, val)

    if 'b' in TEXTFILE_FILE_WRITE_MODE:
        try:
            contents = bytearray(contents, 'utf-8')
        except TypeError:
            # TODO: this should not happen, get_text_contents returns text
            contents = bytearray(contents)

    return contents


def _action(target, source, env):

    # prepare the line separator
    linesep = env['LINESEPARATOR']
    if linesep is None:
        linesep = LINESEP  # os.linesep
    elif is_String(linesep):
        pass
    elif isinstance(linesep, Value):
        linesep = linesep.get_text_contents()
    else:
        raise SCons.Errors.UserError('unexpected type/class for LINESEPARATOR: %s'
                                     % repr(linesep), None)

    if 'b' in TEXTFILE_FILE_WRITE_MODE:
        linesep = to_bytes(linesep)

    # create a dictionary to use for the substitutions
    if 'SUBST_DICT' not in env:
        subs = None    # no substitutions
    else:
        subst_dict = env['SUBST_DICT']
        if is_Dict(subst_dict):
            subst_dict = list(subst_dict.items())
        elif is_Sequence(subst_dict):
            pass
        else:
            raise SCons.Errors.UserError('SUBST_DICT must be dict or sequence')
        subs = []
        for (k, value) in subst_dict:
            if callable(value):
                value = value()
            if is_String(value):
                value = env.subst(value, raw=1)
            else:
                value = str(value)
            subs.append((k, value))

    # write the file
    try:
        target_file = open(target[0].get_path(), TEXTFILE_FILE_WRITE_MODE, newline='')
    except (OSError, IOError) as e:
        raise SCons.Errors.UserError("Can't write target file %s [%s]" % (target[0],e))

    # separate lines by 'linesep' only if linesep is not empty
    lsep = None
    for line in source:
        if lsep:
            target_file.write(lsep)

        target_file.write(_do_subst(line, subs))
        lsep = linesep
    target_file.close()


def _strfunc(target, source, env):
    return "Creating '%s'" % target[0]


def _convert_list_R(newlist, sources):
    for elem in sources:
        if is_Sequence(elem):
            _convert_list_R(newlist, elem)
        elif isinstance(elem, Node):
            newlist.append(elem)
        else:
            newlist.append(Value(elem))


def _convert_list(target, source, env):
    if len(target) != 1:
        raise SCons.Errors.UserError("Only one target file allowed")
    newlist = []
    _convert_list_R(newlist, source)
    return target, newlist


_common_varlist = ['SUBST_DICT', 'LINESEPARATOR']

_text_varlist = _common_varlist + ['TEXTFILEPREFIX', 'TEXTFILESUFFIX']
_text_builder = SCons.Builder.Builder(
    action=SCons.Action.Action(_action, _strfunc, varlist=_text_varlist),
    source_factory=Value,
    emitter=_convert_list,
    prefix='$TEXTFILEPREFIX',
    suffix='$TEXTFILESUFFIX',
)

_subst_varlist = _common_varlist + ['SUBSTFILEPREFIX', 'SUBSTFILESUFFIX']
_subst_builder = SCons.Builder.Builder(
    action=SCons.Action.Action(_action, _strfunc, varlist=_subst_varlist),
    source_factory=SCons.Node.FS.File,
    emitter=_convert_list,
    prefix='$SUBSTFILEPREFIX',
    suffix='$SUBSTFILESUFFIX',
    src_suffix=['.in'],
)


def generate(env):
    env['LINESEPARATOR'] = LINESEP # os.linesep
    env['BUILDERS']['Textfile'] = _text_builder
    env['TEXTFILEPREFIX'] = ''
    env['TEXTFILESUFFIX'] = '.txt'
    env['BUILDERS']['Substfile'] = _subst_builder
    env['SUBSTFILEPREFIX'] = ''
    env['SUBSTFILESUFFIX'] = ''


def exists(env):
    return 1

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/tlib.py

"""SCons.Tool.tlib

XXX

"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import SCons.Tool
import SCons.Tool.bcc32
import SCons.Util

def generate(env):
    SCons.Tool.bcc32.findIt('tlib', env)
    """Add Builders and construction variables for ar to an Environment."""
    SCons.Tool.createStaticLibBuilder(env)
    env['AR']          = 'tlib'
    env['ARFLAGS']     = SCons.Util.CLVar('')
    env['ARCOM']       = '$AR $TARGET $ARFLAGS /a $SOURCES'
    env['LIBPREFIX']   = ''
    env['LIBSUFFIX']   = '.lib'

def exists(env):
    return SCons.Tool.bcc32.findIt('tlib', env)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/wix.py

"""SCons.Tool.wix

Tool-specific initialization for wix, the Windows Installer XML Tool.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import SCons.Builder
import SCons.Action
import os

def generate(env):
    """Add Builders and construction variables for WiX to an Environment."""
    if not exists(env):
      return

    env['WIXCANDLEFLAGS'] = ['-nologo']
    env['WIXCANDLEINCLUDE'] = []
    env['WIXCANDLECOM'] = '$WIXCANDLE $WIXCANDLEFLAGS -I $WIXCANDLEINCLUDE -o ${TARGET} ${SOURCE}'

    env['WIXLIGHTFLAGS'].append( '-nologo' )
    env['WIXLIGHTCOM'] = "$WIXLIGHT $WIXLIGHTFLAGS -out ${TARGET} ${SOURCES}"
    env['WIXSRCSUF'] = '.wxs'
    env['WIXOBJSUF'] = '.wixobj'

    object_builder = SCons.Builder.Builder(
        action      = '$WIXCANDLECOM',
        suffix      = '$WIXOBJSUF',
        src_suffix  = '$WIXSRCSUF')

    linker_builder = SCons.Builder.Builder(
        action      = '$WIXLIGHTCOM',
        src_suffix  = '$WIXOBJSUF',
        src_builder = object_builder)

    env['BUILDERS']['WiX'] = linker_builder

def exists(env):
    env['WIXCANDLE'] = 'candle.exe'
    env['WIXLIGHT']  = 'light.exe'

    # try to find the candle.exe and light.exe tools and 
    # add the install directory to light libpath.
    for path in os.environ['PATH'].split(os.pathsep):
        if not path:
            continue

        # workaround for some weird python win32 bug.
        if path[0] == '"' and path[-1:]=='"':
            path = path[1:-1]

        # normalize the path
        path = os.path.normpath(path)

        # search for the tools in the PATH environment variable
        try:
            files = os.listdir(path)
            if env['WIXCANDLE'] in files and env['WIXLIGHT'] in files:
                env.PrependENVPath('PATH', path)
                # include appropriate flags if running WiX 2.0
                if 'wixui.wixlib' in files and 'WixUI_en-us.wxl' in files:
                    env['WIXLIGHTFLAGS'] = [ os.path.join( path, 'wixui.wixlib' ),
                                             '-loc',
                                             os.path.join( path, 'WixUI_en-us.wxl' ) ]
                else:
                    env['WIXLIGHTFLAGS'] = []
                return 1
        except OSError:
            pass # ignore this, could be a stale PATH entry.

    return None

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/wixTests.py

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import unittest
import os.path
import os

import SCons.Errors
from SCons.Tool.wix import *
from SCons.Environment import Environment

import TestCmd


# create fake candle and light, so the tool's exists() method will succeed
test = TestCmd.TestCmd(workdir = '')
test.write('candle.exe', 'rem this is candle')
test.write('light.exe', 'rem this is light')
os.environ['PATH'] += os.pathsep + test.workdir

class WixTestCase(unittest.TestCase):
    def test_vars(self):
        """Test that WiX tool adds vars"""
        env = Environment(tools=['wix'])
        assert env['WIXCANDLE'] is not None
        assert env['WIXCANDLEFLAGS'] is not None
        assert env['WIXLIGHTFLAGS'] is not None
        assert env.subst('$WIXOBJSUF') == '.wixobj'
        assert env.subst('$WIXSRCSUF') == '.wxs'

if __name__ == "__main__":
    unittest.main()

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/xgettext.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""Tool specific initialization of `xgettext` tool."""

import os
import re
import subprocess
import sys

import SCons.Action
import SCons.Node.FS
import SCons.Tool
import SCons.Util
import SCons.Warnings
from SCons.Builder import BuilderBase
from SCons.Environment import _null
from SCons.Platform.cygwin import CYGWIN_DEFAULT_PATHS
from SCons.Platform.mingw import MINGW_DEFAULT_PATHS
from SCons.Tool.GettextCommon import (
    _detect_xgettext,
    _POTargetFactory,
    RPaths,
    _xgettext_exists,
    # XgettextToolWarning,
)


class _CmdRunner:
    """ Callable object, which runs shell command storing its stdout and stderr to
    variables. It also provides `strfunction()` method, which shall be used by
    scons Action objects to print command string. """

    def __init__(self, command, commandstr=None):
        self.out = None
        self.err = None
        self.status = None
        self.command = command
        self.commandstr = commandstr

    def __call__(self, target, source, env):
        kw = {
            'stdin': 'devnull',
            'stdout': subprocess.PIPE,
            'stderr': subprocess.PIPE,
            'universal_newlines': True,
            'shell': True
        }
        command = env.subst(self.command, target=target, source=source)
        proc = SCons.Action._subproc(env, command, **kw)
        self.out, self.err = proc.communicate()
        self.status = proc.wait()
        if self.err:
            sys.stderr.write(str(self.err))
        return self.status

    def strfunction(self, target, source, env):
        comstr = self.commandstr
        if env.subst(comstr, target=target, source=source) == "":
            comstr = self.command
        s = env.subst(comstr, target=target, source=source)
        return s


def _update_pot_file(target, source, env):
    """ Action function for `POTUpdate` builder """
    nop = lambda target, source, env: 0

    # Save scons cwd and os cwd (NOTE: they may be different. After the job, we
    # revert each one to its original state).
    save_cwd = env.fs.getcwd()
    save_os_cwd = os.getcwd()
    chdir = target[0].dir
    chdir_str = repr(chdir.get_abspath())
    # Print chdir message (employ SCons.Action.Action for that. It knows better
    # than me how to to this correctly).
    env.Execute(SCons.Action.Action(nop, "Entering " + chdir_str))
    # Go to target's directory and do our job
    env.fs.chdir(chdir, 1)  # Go into target's directory
    try:
        cmd = _CmdRunner('$XGETTEXTCOM', '$XGETTEXTCOMSTR')
        action = SCons.Action.Action(cmd, strfunction=cmd.strfunction)
        status = action([target[0]], source, env)
    except:
        # Something went wrong.
        env.Execute(SCons.Action.Action(nop, "Leaving " + chdir_str))
        # Revert working dirs to previous state and re-throw exception.
        env.fs.chdir(save_cwd, 0)
        os.chdir(save_os_cwd)
        raise
    # Print chdir message.
    env.Execute(SCons.Action.Action(nop, "Leaving " + chdir_str))
    # Revert working dirs to previous state.
    env.fs.chdir(save_cwd, 0)
    os.chdir(save_os_cwd)
    # If the command was not successfull, return error code.
    if status: return status

    new_content = cmd.out

    if not new_content:
        # When xgettext finds no internationalized messages, no *.pot is created
        # (because we don't want to bother translators with empty POT files).
        needs_update = False
        explain = "no internationalized messages encountered"
    else:
        if target[0].exists():
            # If the file already exists, it's left unaltered unless its messages
            # are outdated (w.r.t. to these recovered by xgettext from sources).
            old_content = target[0].get_text_contents()
            re_cdate = re.compile(r'^"POT-Creation-Date: .*"$[\r\n]?', re.M)
            old_content_nocdate = re.sub(re_cdate, "", old_content)
            new_content_nocdate = re.sub(re_cdate, "", new_content)
            if old_content_nocdate == new_content_nocdate:
                # Messages are up-to-date
                needs_update = False
                explain = "messages in file found to be up-to-date"
            else:
                # Messages are outdated
                needs_update = True
                explain = "messages in file were outdated"
        else:
            # No POT file found, create new one
            needs_update = True
            explain = "new file"
    if needs_update:
        # Print message employing SCons.Action.Action for that.
        msg = "Writing " + repr(str(target[0])) + " (" + explain + ")"
        env.Execute(SCons.Action.Action(nop, msg))
        f = open(str(target[0]), "w")
        f.write(new_content)
        f.close()
        return 0
    else:
        # Print message employing SCons.Action.Action for that.
        msg = "Not writing " + repr(str(target[0])) + " (" + explain + ")"
        env.Execute(SCons.Action.Action(nop, msg))
        return 0


class _POTBuilder(BuilderBase):
    def _execute(self, env, target, source, *args):
        if not target:
            if 'POTDOMAIN' in env and env['POTDOMAIN']:
                domain = env['POTDOMAIN']
            else:
                domain = 'messages'
            target = [domain]
        return BuilderBase._execute(self, env, target, source, *args)


def _scan_xgettext_from_files(target, source, env, files=None, path=None):
    """ Parses `POTFILES.in`-like file and returns list of extracted file names.
    """
    if files is None:
        return 0
    if not SCons.Util.is_List(files):
        files = [files]

    if path is None:
        if 'XGETTEXTPATH' in env:
            path = env['XGETTEXTPATH']
        else:
            path = []
    if not SCons.Util.is_List(path):
        path = [path]

    path = SCons.Util.flatten(path)

    dirs = ()
    for p in path:
        if not isinstance(p, SCons.Node.FS.Base):
            if SCons.Util.is_String(p):
                p = env.subst(p, source=source, target=target)
            p = env.arg2nodes(p, env.fs.Dir)
        dirs += tuple(p)
    # cwd is the default search path (when no path is defined by user)
    if not dirs:
        dirs = (env.fs.getcwd(),)

    # Parse 'POTFILE.in' files.
    re_comment = re.compile(r'^#[^\n\r]*$\r?\n?', re.M)
    re_emptyln = re.compile(r'^[ \t\r]*$\r?\n?', re.M)
    re_trailws = re.compile(r'[ \t\r]+$')
    for f in files:
        # Find files in search path $XGETTEXTPATH
        if isinstance(f, SCons.Node.FS.Base) and f.rexists():
            contents = f.get_text_contents()
            contents = re_comment.sub("", contents)
            contents = re_emptyln.sub("", contents)
            contents = re_trailws.sub("", contents)
            depnames = contents.splitlines()
            for depname in depnames:
                depfile = SCons.Node.FS.find_file(depname, dirs)
                if not depfile:
                    depfile = env.arg2nodes(depname, dirs[0].File)
                env.Depends(target, depfile)
    return 0


def _pot_update_emitter(target, source, env):
    """ Emitter function for `POTUpdate` builder """
    if 'XGETTEXTFROM' in env:
        xfrom = env['XGETTEXTFROM']
    else:
        return target, source
    if not SCons.Util.is_List(xfrom):
        xfrom = [xfrom]

    xfrom = SCons.Util.flatten(xfrom)

    # Prepare list of 'POTFILE.in' files.
    files = []
    for xf in xfrom:
        if not isinstance(xf, SCons.Node.FS.Base):
            if SCons.Util.is_String(xf):
                # Interpolate variables in strings
                xf = env.subst(xf, source=source, target=target)
            xf = env.arg2nodes(xf)
        files.extend(xf)
    if files:
        env.Depends(target, files)
        _scan_xgettext_from_files(target, source, env, files)
    return target, source


def _POTUpdateBuilderWrapper(env, target=None, source=_null, **kw):
    return env._POTUpdateBuilder(target, source, **kw)


def _POTUpdateBuilder(env, **kw):
    """ Creates `POTUpdate` builder object """
    kw['action'] = SCons.Action.Action(_update_pot_file, None)
    kw['suffix'] = '$POTSUFFIX'
    kw['target_factory'] = _POTargetFactory(env, alias='$POTUPDATE_ALIAS').File
    kw['emitter'] = _pot_update_emitter
    return _POTBuilder(**kw)


def generate(env, **kw):
    """ Generate `xgettext` tool """

    if sys.platform == 'win32':
        xgettext = SCons.Tool.find_program_path(env, 'xgettext', default_paths=MINGW_DEFAULT_PATHS + CYGWIN_DEFAULT_PATHS )
        if xgettext:
            xgettext_bin_dir = os.path.dirname(xgettext)
            env.AppendENVPath('PATH', xgettext_bin_dir)
        else:
            SCons.Warnings.warn(
                # XgettextToolWarning,  # using this breaks test, so keep:
                SCons.Warnings.SConsWarning,
                'xgettext tool requested, but binary not found in ENV PATH'
            )
    try:
        env['XGETTEXT'] = _detect_xgettext(env)
    except:
        env['XGETTEXT'] = 'xgettext'
    # NOTE: sources="$SOURCES" would work as well. However, we use following
    # construction to convert absolute paths provided by scons onto paths
    # relative to current working dir. Note, that scons expands $SOURCE(S) to
    # absolute paths for sources $SOURCE(s) outside of current subtree (e.g. in
    # "../"). With source=$SOURCE these absolute paths would be written to the
    # resultant *.pot file (and its derived *.po files) as references to lines in
    # source code (e.g. referring lines in *.c files). Such references would be
    # correct (e.g. in poedit) only on machine on which *.pot was generated and
    # would be of no use on other hosts (having a copy of source code located
    # in different place in filesystem).
    sources = '$( ${_concat( "", SOURCES, "", __env__, XgettextRPaths, TARGET' \
              + ', SOURCES)} $)'

    # NOTE: the output from $XGETTEXTCOM command must go to stdout, not to a file.
    # This is required by the POTUpdate builder's action.
    xgettextcom = '$XGETTEXT $XGETTEXTFLAGS $_XGETTEXTPATHFLAGS' \
                  + ' $_XGETTEXTFROMFLAGS -o - ' + sources

    xgettextpathflags = '$( ${_concat( XGETTEXTPATHPREFIX, XGETTEXTPATH' \
                        + ', XGETTEXTPATHSUFFIX, __env__, RDirs, TARGET, SOURCES)} $)'
    xgettextfromflags = '$( ${_concat( XGETTEXTFROMPREFIX, XGETTEXTFROM' \
                        + ', XGETTEXTFROMSUFFIX, __env__, target=TARGET, source=SOURCES)} $)'

    env.SetDefault(
        _XGETTEXTDOMAIN='${TARGET.filebase}',
        XGETTEXTFLAGS=[],
        XGETTEXTCOM=xgettextcom,
        XGETTEXTCOMSTR='',
        XGETTEXTPATH=[],
        XGETTEXTPATHPREFIX='-D',
        XGETTEXTPATHSUFFIX='',
        XGETTEXTFROM=None,
        XGETTEXTFROMPREFIX='-f',
        XGETTEXTFROMSUFFIX='',
        _XGETTEXTPATHFLAGS=xgettextpathflags,
        _XGETTEXTFROMFLAGS=xgettextfromflags,
        POTSUFFIX=['.pot'],
        POTUPDATE_ALIAS='pot-update',
        XgettextRPaths=RPaths(env)
    )
    env.Append(BUILDERS={
        '_POTUpdateBuilder': _POTUpdateBuilder(env)
    })
    env.AddMethod(_POTUpdateBuilderWrapper, 'POTUpdate')
    env.AlwaysBuild(env.Alias('$POTUPDATE_ALIAS'))


def exists(env):
    """ Check, whether the tool exists """
    try:
        return _xgettext_exists(env)
    except:
        return False


# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/yacc.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""Tool-specific initialization for yacc.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.
"""

import os.path
import sys

import SCons.Defaults
import SCons.Tool
import SCons.Util
import SCons.Warnings
from SCons.Platform.mingw import MINGW_DEFAULT_PATHS
from SCons.Platform.cygwin import CYGWIN_DEFAULT_PATHS
from SCons.Platform.win32 import CHOCO_DEFAULT_PATH

YaccAction = SCons.Action.Action("$YACCCOM", "$YACCCOMSTR")

if sys.platform == 'win32':
    BINS = ['bison', 'yacc', 'win_bison']
else:
    BINS = ["bison", "yacc"]


def _yaccEmitter(target, source, env, ysuf, hsuf):
    yaccflags = env.subst("$YACCFLAGS", target=target, source=source)
    flags = SCons.Util.CLVar(yaccflags)
    targetBase, targetExt = os.path.splitext(SCons.Util.to_String(target[0]))

    if '.ym' in ysuf:  # If using Objective-C
        target = [targetBase + ".m"]  # the extension is ".m".

    # If -d is specified on the command line, yacc will emit a .h
    # or .hpp file with the same name as the .c or .cpp output file.
    if '-d' in flags:
        target.append(targetBase + env.subst(hsuf, target=target, source=source))

    # If -g is specified on the command line, yacc will emit a .vcg
    # file with the same base name as the .y, .yacc, .ym or .yy file.
    if "-g" in flags:
        base, ext = os.path.splitext(SCons.Util.to_String(source[0]))
        target.append(base + env.subst("$YACCVCGFILESUFFIX"))

    # If -v is specified yacc will create the output debug file
    # which is not really source for any process, but should
    # be noted and also be cleaned
    # Bug #2558
    if "-v" in flags:
        env.SideEffect(targetBase + '.output', target[0])
        env.Clean(target[0], targetBase + '.output')

    # With --defines and --graph, the name of the file is totally defined
    # in the options.
    fileGenOptions = ["--defines=", "--graph="]
    for option in flags:
        for fileGenOption in fileGenOptions:
            l = len(fileGenOption)
            if option[:l] == fileGenOption:
                # A file generating option is present, so add the file
                # name to the list of targets.
                fileName = option[l:].strip()
                target.append(fileName)

    return (target, source)


def yEmitter(target, source, env):
    return _yaccEmitter(target, source, env, ['.y', '.yacc'], '$YACCHFILESUFFIX')


def ymEmitter(target, source, env):
    return _yaccEmitter(target, source, env, ['.ym'], '$YACCHFILESUFFIX')


def yyEmitter(target, source, env):
    return _yaccEmitter(target, source, env, ['.yy'], '$YACCHXXFILESUFFIX')


def get_yacc_path(env, append_paths=False):
    """
    Find the path to the yacc tool, searching several possible names

    Only called in the Windows case, so the default_path
    can be Windows-specific

    :param env: current construction environment
    :param append_paths: if set, add the path to the tool to PATH
    :return: path to yacc tool, if found
    """
    for prog in BINS:
        bin_path = SCons.Tool.find_program_path(
            env,
            prog,
            default_paths=CHOCO_DEFAULT_PATH + MINGW_DEFAULT_PATHS + CYGWIN_DEFAULT_PATHS)
        if bin_path:
            if append_paths:
                env.AppendENVPath('PATH', os.path.dirname(bin_path))
            return bin_path
    SCons.Warnings.warn(
        SCons.Warnings.SConsWarning,
        'yacc tool requested, but yacc or bison binary not found in ENV PATH'
    )


def generate(env):
    """Add Builders and construction variables for yacc to an Environment."""
    c_file, cxx_file = SCons.Tool.createCFileBuilders(env)

    # C
    c_file.add_action('.y', YaccAction)
    c_file.add_emitter('.y', yEmitter)

    c_file.add_action('.yacc', YaccAction)
    c_file.add_emitter('.yacc', yEmitter)

    # Objective-C
    c_file.add_action('.ym', YaccAction)
    c_file.add_emitter('.ym', ymEmitter)

    # C++
    cxx_file.add_action('.yy', YaccAction)
    cxx_file.add_emitter('.yy', yyEmitter)

    if sys.platform == 'win32':
        # ignore the return, all we need is for the path to be added
        _ = get_yacc_path(env, append_paths=True)

    if 'YACC' not in env:
        env["YACC"] = env.Detect(BINS)

    env['YACCFLAGS'] = SCons.Util.CLVar('')
    env['YACCCOM'] = '$YACC $YACCFLAGS -o $TARGET $SOURCES'
    env['YACCHFILESUFFIX'] = '.h'
    env['YACCHXXFILESUFFIX'] = '.hpp'
    env['YACCVCGFILESUFFIX'] = '.vcg'


def exists(env):
    if 'YACC' in env:
        return env.Detect(env['YACC'])

    if sys.platform == 'win32':
        return get_yacc_path(env)
    else:
        return env.Detect(BINS)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/zip.py

"""SCons.Tool.zip

Tool-specific initialization for zip.

There normally shouldn't be any need to import this module directly.
It will usually be imported through the generic SCons.Tool.Tool()
selection method.

"""

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

import os

import SCons.Builder
import SCons.Defaults
import SCons.Node.FS
import SCons.Util

import time
import zipfile


zip_compression = zipfile.ZIP_DEFLATED


def _create_zipinfo_for_file(fname, arcname, date_time, compression):
    st = os.stat(fname)
    if not date_time:
        mtime = time.localtime(st.st_mtime)
        date_time = mtime[0:6]
    zinfo = zipfile.ZipInfo(filename=arcname, date_time=date_time)
    zinfo.external_attr = (st.st_mode & 0xFFFF) << 16  # Unix attributes
    zinfo.compress_type = compression
    zinfo.file_size = st.st_size
    return zinfo


def zip_builder(target, source, env):
    compression = env.get('ZIPCOMPRESSION', zipfile.ZIP_STORED)
    zip_root = str(env.get('ZIPROOT', ''))
    date_time = env.get('ZIP_OVERRIDE_TIMESTAMP')

    files = []
    for s in source:
        if s.isdir():
            for dirpath, dirnames, filenames in os.walk(str(s)):
                for fname in filenames:
                    path = os.path.join(dirpath, fname)
                    if os.path.isfile(path):
                        files.append(path)
        else:
            files.append(str(s))

    with zipfile.ZipFile(str(target[0]), 'w', compression) as zf:
        for fname in files:
            arcname = os.path.relpath(fname, zip_root)
            # TODO: Switch to ZipInfo.from_file when 3.6 becomes the base python version
            zinfo = _create_zipinfo_for_file(fname, arcname, date_time, compression)
            with open(fname, "rb") as f:
                zf.writestr(zinfo, f.read())


# Fix PR #3569 - If you don't specify ZIPCOM and ZIPCOMSTR when creating
# env, then it will ignore ZIPCOMSTR set afterwards.
zipAction = SCons.Action.Action(zip_builder, "$ZIPCOMSTR",
                                varlist=['ZIPCOMPRESSION', 'ZIPROOT', 'ZIP_OVERRIDE_TIMESTAMP'])

ZipBuilder = SCons.Builder.Builder(action=SCons.Action.Action('$ZIPCOM', '$ZIPCOMSTR'),
                                   source_factory=SCons.Node.FS.Entry,
                                   source_scanner=SCons.Defaults.DirScanner,
                                   suffix='$ZIPSUFFIX',
                                   multi=1)


def generate(env):
    """Add Builders and construction variables for zip to an Environment."""
    try:
        bld = env['BUILDERS']['Zip']
    except KeyError:
        bld = ZipBuilder
        env['BUILDERS']['Zip'] = bld

    env['ZIP'] = 'zip'
    env['ZIPFLAGS'] = SCons.Util.CLVar('')
    env['ZIPCOM'] = zipAction
    env['ZIPCOMPRESSION'] = zip_compression
    env['ZIPSUFFIX'] = '.zip'
    env['ZIPROOT'] = SCons.Util.CLVar('')


def exists(env):
    return True

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/MSCommon/__init__.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""
Common functions for Microsoft Visual Studio and Visual C/C++.
"""


import SCons.Errors
import SCons.Platform.win32
import SCons.Util

from SCons.Tool.MSCommon.sdk import mssdk_exists, mssdk_setup_env

from SCons.Tool.MSCommon.vc import (
    msvc_exists,
    msvc_setup_env_tool,
    msvc_setup_env_once,
    msvc_version_to_maj_min,
    msvc_find_vswhere,
    set_msvc_notfound_policy,
    get_msvc_notfound_policy,
    get_msvc_sdk_versions,
)

from SCons.Tool.MSCommon.vs import (
    get_default_version,
    get_vs_by_version,
    merge_default_version,
    msvs_exists,
    query_versions,
)

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/MSCommon/arch.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""
MS compilers: Supported Windows chip architectures.
"""


class ArchDefinition:
    """
    A class for defining architecture-specific settings and logic.
    """
    def __init__(self, arch, synonyms=[]):
        self.arch = arch
        self.synonyms = synonyms

SupportedArchitectureList = [
    ArchDefinition(
        'x86',
        ['i386', 'i486', 'i586', 'i686'],
    ),
    ArchDefinition(
        'x86_64',
        ['AMD64', 'amd64', 'em64t', 'EM64T', 'x86_64'],
    ),
    ArchDefinition(
        'ia64',
        ['IA64'],
    ),
    ArchDefinition(
        'arm',
        ['ARM'],
    ),
]

SupportedArchitectureMap = {}
for a in SupportedArchitectureList:
    SupportedArchitectureMap[a.arch] = a
    for s in a.synonyms:
        SupportedArchitectureMap[s] = a

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/MSCommon/common.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""
Common helper functions for working with the Microsoft tool chain.
"""

import copy
import json
import os
import re
import subprocess
import sys
from contextlib import suppress
from pathlib import Path

import SCons.Util
import SCons.Warnings

class MSVCCacheInvalidWarning(SCons.Warnings.WarningOnByDefault):
    pass

# SCONS_MSCOMMON_DEBUG is internal-use so undocumented:
# set to '-' to print to console, else set to filename to log to
LOGFILE = os.environ.get('SCONS_MSCOMMON_DEBUG')
if LOGFILE:
    import logging
    modulelist = (
        # root module and parent/root module
        'MSCommon', 'Tool',
        # python library and below: correct iff scons does not have a lib folder
        'lib',
        # scons modules
        'SCons', 'test', 'scons'
    )
    def get_relative_filename(filename, module_list):
        if not filename:
            return filename
        for module in module_list:
            try:
                ind = filename.rindex(module)
                return filename[ind:]
            except ValueError:
                pass
        return filename
    class _Debug_Filter(logging.Filter):
        # custom filter for module relative filename
        def filter(self, record):
            relfilename = get_relative_filename(record.pathname, modulelist)
            relfilename = relfilename.replace('\\', '/')
            record.relfilename = relfilename
            return True
    # Log format looks like:
    #   00109ms:MSCommon/vc.py:find_vc_pdir#447: VC found '14.3'        [file]
    #   debug: 00109ms:MSCommon/vc.py:find_vc_pdir#447: VC found '14.3' [stdout]
    log_format=(
        '%(relativeCreated)05dms'
        ':%(relfilename)s'
        ':%(funcName)s'
        '#%(lineno)s'
        ': %(message)s'
    )
    if LOGFILE == '-':
        log_format = 'debug: ' + log_format
        log_handler = logging.StreamHandler(sys.stdout)
    else:
        log_handler = logging.FileHandler(filename=LOGFILE)
    logging.basicConfig(
        format=log_format,
        handlers=[log_handler],
        level=logging.DEBUG)
    logger = logging.getLogger(name=__name__)
    logger.addFilter(_Debug_Filter())
    debug = logger.debug
else:
    def debug(x, *args):
        return None


# SCONS_CACHE_MSVC_CONFIG is public, and is documented.
CONFIG_CACHE = os.environ.get('SCONS_CACHE_MSVC_CONFIG')
if CONFIG_CACHE in ('1', 'true', 'True'):
    CONFIG_CACHE = os.path.join(os.path.expanduser('~'), 'scons_msvc_cache.json')


def read_script_env_cache():
    """ fetch cached msvc env vars if requested, else return empty dict """
    envcache = {}
    if CONFIG_CACHE:
        try:
            p = Path(CONFIG_CACHE)
            with p.open('r') as f:
                # Convert the list of cache entry dictionaries read from
                # json to the cache dictionary. Reconstruct the cache key
                # tuple from the key list written to json.
                envcache_list = json.load(f)
                if isinstance(envcache_list, list):
                    envcache = {tuple(d['key']): d['data'] for d in envcache_list}
                else:
                    # don't fail if incompatible format, just proceed without it
                    warn_msg = "Incompatible format for msvc cache file {}: file may be overwritten.".format(
                        repr(CONFIG_CACHE)
                    )
                    SCons.Warnings.warn(MSVCCacheInvalidWarning, warn_msg)
                    debug(warn_msg)
        except FileNotFoundError:
            # don't fail if no cache file, just proceed without it
            pass
    return envcache


def write_script_env_cache(cache):
    """ write out cache of msvc env vars if requested """
    if CONFIG_CACHE:
        try:
            p = Path(CONFIG_CACHE)
            with p.open('w') as f:
                # Convert the cache dictionary to a list of cache entry
                # dictionaries. The cache key is converted from a tuple to
                # a list for compatibility with json.
                envcache_list = [{'key': list(key), 'data': data} for key, data in cache.items()]
                json.dump(envcache_list, f, indent=2)
        except TypeError:
            # data can't serialize to json, don't leave partial file
            with suppress(FileNotFoundError):
                p.unlink()
        except IOError:
            # can't write the file, just skip
            pass


_is_win64 = None


def is_win64():
    """Return true if running on windows 64 bits.

    Works whether python itself runs in 64 bits or 32 bits."""
    # Unfortunately, python does not provide a useful way to determine
    # if the underlying Windows OS is 32-bit or 64-bit.  Worse, whether
    # the Python itself is 32-bit or 64-bit affects what it returns,
    # so nothing in sys.* or os.* help.

    # Apparently the best solution is to use env vars that Windows
    # sets.  If PROCESSOR_ARCHITECTURE is not x86, then the python
    # process is running in 64 bit mode (on a 64-bit OS, 64-bit
    # hardware, obviously).
    # If this python is 32-bit but the OS is 64, Windows will set
    # ProgramW6432 and PROCESSOR_ARCHITEW6432 to non-null.
    # (Checking for HKLM\Software\Wow6432Node in the registry doesn't
    # work, because some 32-bit installers create it.)
    global _is_win64
    if _is_win64 is None:
        # I structured these tests to make it easy to add new ones or
        # add exceptions in the future, because this is a bit fragile.
        _is_win64 = False
        if os.environ.get('PROCESSOR_ARCHITECTURE', 'x86') != 'x86':
            _is_win64 = True
        if os.environ.get('PROCESSOR_ARCHITEW6432'):
            _is_win64 = True
        if os.environ.get('ProgramW6432'):
            _is_win64 = True
    return _is_win64


def read_reg(value, hkroot=SCons.Util.HKEY_LOCAL_MACHINE):
    return SCons.Util.RegGetValue(hkroot, value)[0]


def has_reg(value):
    """Return True if the given key exists in HKEY_LOCAL_MACHINE, False
    otherwise."""
    try:
        SCons.Util.RegOpenKeyEx(SCons.Util.HKEY_LOCAL_MACHINE, value)
        ret = True
    except OSError:
        ret = False
    return ret

# Functions for fetching environment variable settings from batch files.


def normalize_env(env, keys, force=False):
    """Given a dictionary representing a shell environment, add the variables
    from os.environ needed for the processing of .bat files; the keys are
    controlled by the keys argument.

    It also makes sure the environment values are correctly encoded.

    If force=True, then all of the key values that exist are copied
    into the returned dictionary.  If force=false, values are only
    copied if the key does not already exist in the copied dictionary.

    Note: the environment is copied."""
    normenv = {}
    if env:
        for k, v in env.items():
            normenv[k] = copy.deepcopy(v)

        for k in keys:
            if k in os.environ and (force or k not in normenv):
                normenv[k] = os.environ[k]

    # add some things to PATH to prevent problems:
    # Shouldn't be necessary to add system32, since the default environment
    # should include it, but keep this here to be safe (needed for reg.exe)
    sys32_dir = os.path.join(
        os.environ.get("SystemRoot", os.environ.get("windir", r"C:\Windows")), "System32"
)
    if sys32_dir not in normenv["PATH"]:
        normenv["PATH"] = normenv["PATH"] + os.pathsep + sys32_dir

    # Without Wbem in PATH, vcvarsall.bat has a "'wmic' is not recognized"
    # error starting with Visual Studio 2017, although the script still
    # seems to work anyway.
    sys32_wbem_dir = os.path.join(sys32_dir, 'Wbem')
    if sys32_wbem_dir not in normenv['PATH']:
        normenv['PATH'] = normenv['PATH'] + os.pathsep + sys32_wbem_dir

    # Without Powershell in PATH, an internal call to a telemetry
    # function (starting with a VS2019 update) can fail
    # Note can also set VSCMD_SKIP_SENDTELEMETRY to avoid this.
    sys32_ps_dir = os.path.join(sys32_dir, r'WindowsPowerShell\v1.0')
    if sys32_ps_dir not in normenv['PATH']:
        normenv['PATH'] = normenv['PATH'] + os.pathsep + sys32_ps_dir

    debug("PATH: %s", normenv['PATH'])
    return normenv


def get_output(vcbat, args=None, env=None):
    """Parse the output of given bat file, with given args."""

    if env is None:
        # Create a blank environment, for use in launching the tools
        env = SCons.Environment.Environment(tools=[])

    # TODO:  Hard-coded list of the variables that (may) need to be
    # imported from os.environ[] for the chain of development batch
    # files to execute correctly. One call to vcvars*.bat may
    # end up running a dozen or more scripts, changes not only with
    # each release but with what is installed at the time. We think
    # in modern installations most are set along the way and don't
    # need to be picked from the env, but include these for safety's sake.
    # Any VSCMD variables definitely are picked from the env and
    # control execution in interesting ways.
    # Note these really should be unified - either controlled by vs.py,
    # or synced with the the common_tools_var # settings in vs.py.
    vs_vc_vars = [
        'COMSPEC',  # path to "shell"
        'OS', # name of OS family: Windows_NT or undefined (95/98/ME)
        'VS170COMNTOOLS',  # path to common tools for given version
        'VS160COMNTOOLS',
        'VS150COMNTOOLS',
        'VS140COMNTOOLS',
        'VS120COMNTOOLS',
        'VS110COMNTOOLS',
        'VS100COMNTOOLS',
        'VS90COMNTOOLS',
        'VS80COMNTOOLS',
        'VS71COMNTOOLS',
        'VSCOMNTOOLS',
        'MSDevDir',
        'VSCMD_DEBUG',   # enable logging and other debug aids
        'VSCMD_SKIP_SENDTELEMETRY',
        'windir', # windows directory (SystemRoot not available in 95/98/ME)
    ]
    env['ENV'] = normalize_env(env['ENV'], vs_vc_vars, force=False)

    if args:
        debug("Calling '%s %s'", vcbat, args)
        popen = SCons.Action._subproc(env,
                                      '"%s" %s & set' % (vcbat, args),
                                      stdin='devnull',
                                      stdout=subprocess.PIPE,
                                      stderr=subprocess.PIPE)
    else:
        debug("Calling '%s'", vcbat)
        popen = SCons.Action._subproc(env,
                                      '"%s" & set' % vcbat,
                                      stdin='devnull',
                                      stdout=subprocess.PIPE,
                                      stderr=subprocess.PIPE)

    # Use the .stdout and .stderr attributes directly because the
    # .communicate() method uses the threading module on Windows
    # and won't work under Pythons not built with threading.
    with popen.stdout:
        stdout = popen.stdout.read()
    with popen.stderr:
        stderr = popen.stderr.read()

    # Extra debug logic, uncomment if necessary
    # debug('stdout:%s', stdout)
    # debug('stderr:%s', stderr)

    # Ongoing problems getting non-corrupted text led to this
    # changing to "oem" from "mbcs" - the scripts run presumably
    # attached to a console, so some particular rules apply.
    # Unfortunately, "oem" not defined in Python 3.5, so get another way
    if sys.version_info.major == 3 and sys.version_info.minor < 6:
        from ctypes import windll

        OEM = "cp{}".format(windll.kernel32.GetConsoleOutputCP())
    else:
        OEM = "oem"
    if stderr:
        # TODO: find something better to do with stderr;
        # this at least prevents errors from getting swallowed.
        sys.stderr.write(stderr.decode(OEM))
    if popen.wait() != 0:
        raise IOError(stderr.decode(OEM))

    return stdout.decode(OEM)


KEEPLIST = (
    "INCLUDE",
    "LIB",
    "LIBPATH",
    "PATH",
    "VSCMD_ARG_app_plat",
    "VCINSTALLDIR",  # needed by clang -VS 2017 and newer
    "VCToolsInstallDir",  # needed by clang - VS 2015 and older
)


def parse_output(output, keep=KEEPLIST):
    """
    Parse output from running visual c++/studios vcvarsall.bat and running set
    To capture the values listed in keep
    """

    # dkeep is a dict associating key: path_list, where key is one item from
    # keep, and path_list the associated list of paths
    dkeep = {i: [] for i in keep}

    # rdk will  keep the regex to match the .bat file output line starts
    rdk = {}
    for i in keep:
        rdk[i] = re.compile('%s=(.*)' % i, re.I)

    def add_env(rmatch, key, dkeep=dkeep):
        path_list = rmatch.group(1).split(os.pathsep)
        for path in path_list:
            # Do not add empty paths (when a var ends with ;)
            if path:
                # XXX: For some reason, VC98 .bat file adds "" around the PATH
                # values, and it screws up the environment later, so we strip
                # it.
                path = path.strip('"')
                dkeep[key].append(str(path))

    for line in output.splitlines():
        for k, value in rdk.items():
            match = value.match(line)
            if match:
                add_env(match, k)

    return dkeep

def get_pch_node(env, target, source):
    """
    Get the actual PCH file node
    """
    pch_subst = env.get('PCH', False) and env.subst('$PCH',target=target, source=source, conv=lambda x:x)

    if not pch_subst:
        return ""

    if SCons.Util.is_String(pch_subst):
        pch_subst = target[0].dir.File(pch_subst)

    return pch_subst


# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/MSCommon/netframework.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""
MS Compilers: .Net Framework support
"""

import os
import re

from .common import read_reg, debug

# Original value recorded by dcournapeau
_FRAMEWORKDIR_HKEY_ROOT = r'Software\Microsoft\.NETFramework\InstallRoot'
# On SGK's system
_FRAMEWORKDIR_HKEY_ROOT = r'Software\Microsoft\Microsoft SDKs\.NETFramework\v2.0\InstallationFolder'

def find_framework_root():
    # XXX: find it from environment (FrameworkDir)
    try:
        froot = read_reg(_FRAMEWORKDIR_HKEY_ROOT)
        debug("Found framework install root in registry: %s", froot)
    except OSError:
        debug("Could not read reg key %s", _FRAMEWORKDIR_HKEY_ROOT)
        return None

    if not os.path.exists(froot):
        debug("%s not found on fs", froot)
        return None

    return froot

def query_versions():
    froot = find_framework_root()
    if froot:
        contents = os.listdir(froot)

        l = re.compile('v[0-9]+.*')
        versions = [e for e in contents if l.match(e)]

        def versrt(a,b):
            # since version numbers aren't really floats...
            aa = a[1:]
            bb = b[1:]
            aal = aa.split('.')
            bbl = bb.split('.')
            # sequence comparison in python is lexicographical
            # which is exactly what we want.
            # Note we sort backwards so the highest version is first.
            return (aal > bbl) - (aal < bbl)

        versions.sort(versrt)
    else:
        versions = []

    return versions

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/MSCommon/sdk.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""
MS Compilers: detect the Platform/Windows SDK

PSDK 2003 R1 is the earliest version detected.
"""

import os

import SCons.Errors
import SCons.Util

from .common import debug, read_reg

# SDK Checks. This is of course a mess as everything else on MS platforms. Here
# is what we do to detect the SDK:
#
# For Windows SDK >= 6.0: just look into the registry entries:
#   HKLM\Software\Microsoft\Microsoft SDKs\Windows
# All the keys in there are the available versions.
#
# For Platform SDK before 6.0 (2003 server R1 and R2, etc...), there does not
# seem to be any sane registry key, so the precise location is hardcoded.
#
# For versions below 2003R1, it seems the PSDK is included with Visual Studio?
#
# Also, per the following:
#     http://benjamin.smedbergs.us/blog/tag/atl/
# VC++ Professional comes with the SDK, VC++ Express does not.

# Location of the SDK (checked for 6.1 only)
_CURINSTALLED_SDK_HKEY_ROOT = \
        r"Software\Microsoft\Microsoft SDKs\Windows\CurrentInstallFolder"


class SDKDefinition:
    """
    An abstract base class for trying to find installed SDK directories.
    """
    def __init__(self, version, **kw):
        self.version = version
        self.__dict__.update(kw)

    def find_sdk_dir(self):
        """Try to find the MS SDK from the registry.

        Return None if failed or the directory does not exist.
        """
        if not SCons.Util.can_read_reg:
            debug('find_sdk_dir(): can not read registry')
            return None

        hkey = self.HKEY_FMT % self.hkey_data
        debug('find_sdk_dir(): checking registry: %s', hkey)

        try:
            sdk_dir = read_reg(hkey)
        except OSError:
            debug('find_sdk_dir(): no SDK registry key %s', hkey)
            return None

        debug('find_sdk_dir(): Trying SDK Dir: %s', sdk_dir)

        if not os.path.exists(sdk_dir):
            debug('find_sdk_dir(): %s not on file system', sdk_dir)
            return None

        ftc = os.path.join(sdk_dir, self.sanity_check_file)
        if not os.path.exists(ftc):
            debug("find_sdk_dir(): sanity check %s not found", ftc)
            return None

        return sdk_dir

    def get_sdk_dir(self):
        """Return the MSSSDK given the version string."""
        try:
            return self._sdk_dir
        except AttributeError:
            sdk_dir = self.find_sdk_dir()
            self._sdk_dir = sdk_dir
            return sdk_dir

    def get_sdk_vc_script(self,host_arch, target_arch):
        """ Return the script to initialize the VC compiler installed by SDK
        """

        if host_arch == 'amd64' and target_arch == 'x86':
            # No cross tools needed compiling 32 bits on 64 bit machine
            host_arch=target_arch

        arch_string=target_arch
        if host_arch != target_arch:
            arch_string='%s_%s'%(host_arch,target_arch)

        debug(
            "get_sdk_vc_script():arch_string:%s host_arch:%s target_arch:%s",
            arch_string,
            host_arch,
            target_arch,
        )
        file = self.vc_setup_scripts.get(arch_string, None)
        debug("get_sdk_vc_script():file:%s", file)
        return file

class WindowsSDK(SDKDefinition):
    """
    A subclass for trying to find installed Windows SDK directories.
    """
    HKEY_FMT = r'Software\Microsoft\Microsoft SDKs\Windows\v%s\InstallationFolder'
    def __init__(self, *args, **kw):
        super().__init__(*args, **kw)
        self.hkey_data = self.version

class PlatformSDK(SDKDefinition):
    """
    A subclass for trying to find installed Platform SDK directories.
    """
    HKEY_FMT = r'Software\Microsoft\MicrosoftSDK\InstalledSDKS\%s\Install Dir'
    def __init__(self, *args, **kw):
        super().__init__(*args, **kw)
        self.hkey_data = self.uuid

#
# The list of VC initialization scripts installed by the SDK
# These should be tried if the vcvarsall.bat TARGET_ARCH fails
preSDK61VCSetupScripts = { 'x86'      : r'bin\vcvars32.bat',
                           'amd64'    : r'bin\vcvarsamd64.bat',
                           'x86_amd64': r'bin\vcvarsx86_amd64.bat',
                           'x86_ia64' : r'bin\vcvarsx86_ia64.bat',
                           'ia64'     : r'bin\vcvarsia64.bat'}

SDK61VCSetupScripts = {'x86'      : r'bin\vcvars32.bat',
                       'amd64'    : r'bin\amd64\vcvarsamd64.bat',
                       'x86_amd64': r'bin\x86_amd64\vcvarsx86_amd64.bat',
                       'x86_ia64' : r'bin\x86_ia64\vcvarsx86_ia64.bat',
                       'ia64'     : r'bin\ia64\vcvarsia64.bat'}

SDK70VCSetupScripts =    { 'x86'      : r'bin\vcvars32.bat',
                           'amd64'    : r'bin\vcvars64.bat',
                           'x86_amd64': r'bin\vcvarsx86_amd64.bat',
                           'x86_ia64' : r'bin\vcvarsx86_ia64.bat',
                           'ia64'     : r'bin\vcvarsia64.bat'}

SDK100VCSetupScripts =    {'x86'      : r'bin\vcvars32.bat',
                           'amd64'    : r'bin\vcvars64.bat',
                           'x86_amd64': r'bin\x86_amd64\vcvarsx86_amd64.bat',
                           'x86_arm'  : r'bin\x86_arm\vcvarsx86_arm.bat'}


# The list of support SDKs which we know how to detect.
#
# The first SDK found in the list is the one used by default if there
# are multiple SDKs installed.  Barring good reasons to the contrary,
# this means we should list SDKs from most recent to oldest.
#
# If you update this list, update the documentation in Tool/mssdk.xml.
SupportedSDKList = [
    WindowsSDK('10.0A',
               sanity_check_file=r'bin\SetEnv.Cmd',
               include_subdir='include',
               lib_subdir={
                   'x86'       : ['lib'],
                   'x86_64'    : [r'lib\x64'],
                   'ia64'      : [r'lib\ia64'],
               },
               vc_setup_scripts = SDK70VCSetupScripts,
              ),
    WindowsSDK('10.0',
               sanity_check_file=r'bin\SetEnv.Cmd',
               include_subdir='include',
               lib_subdir={
                   'x86'       : ['lib'],
                   'x86_64'    : [r'lib\x64'],
                   'ia64'      : [r'lib\ia64'],
               },
               vc_setup_scripts = SDK70VCSetupScripts,
              ),
    WindowsSDK('7.1',
               sanity_check_file=r'bin\SetEnv.Cmd',
               include_subdir='include',
               lib_subdir={
                   'x86'       : ['lib'],
                   'x86_64'    : [r'lib\x64'],
                   'ia64'      : [r'lib\ia64'],
               },
               vc_setup_scripts = SDK70VCSetupScripts,
              ),
    WindowsSDK('7.0A',
               sanity_check_file=r'bin\SetEnv.Cmd',
               include_subdir='include',
               lib_subdir={
                   'x86'       : ['lib'],
                   'x86_64'    : [r'lib\x64'],
                   'ia64'      : [r'lib\ia64'],
               },
               vc_setup_scripts = SDK70VCSetupScripts,
              ),
    WindowsSDK('7.0',
               sanity_check_file=r'bin\SetEnv.Cmd',
               include_subdir='include',
               lib_subdir={
                   'x86'       : ['lib'],
                   'x86_64'    : [r'lib\x64'],
                   'ia64'      : [r'lib\ia64'],
               },
               vc_setup_scripts = SDK70VCSetupScripts,
              ),
    WindowsSDK('6.1',
               sanity_check_file=r'bin\SetEnv.Cmd',
               include_subdir='include',
               lib_subdir={
                   'x86'       : ['lib'],
                   'x86_64'    : [r'lib\x64'],
                   'ia64'      : [r'lib\ia64'],
               },
               vc_setup_scripts = SDK61VCSetupScripts,
              ),

    WindowsSDK('6.0A',
               sanity_check_file=r'include\windows.h',
               include_subdir='include',
               lib_subdir={
                   'x86'       : ['lib'],
                   'x86_64'    : [r'lib\x64'],
                   'ia64'      : [r'lib\ia64'],
               },
               vc_setup_scripts = preSDK61VCSetupScripts,
              ),

    WindowsSDK('6.0',
               sanity_check_file=r'bin\gacutil.exe',
               include_subdir='include',
               lib_subdir='lib',
               vc_setup_scripts = preSDK61VCSetupScripts,
              ),

    PlatformSDK('2003R2',
                sanity_check_file=r'SetEnv.Cmd',
                uuid="D2FF9F89-8AA2-4373-8A31-C838BF4DBBE1",
                vc_setup_scripts = preSDK61VCSetupScripts,
               ),

    PlatformSDK('2003R1',
                sanity_check_file=r'SetEnv.Cmd',
                uuid="8F9E5EF3-A9A5-491B-A889-C58EFFECE8B3",
                vc_setup_scripts = preSDK61VCSetupScripts,
               ),
]

SupportedSDKMap = {}
for sdk in SupportedSDKList:
    SupportedSDKMap[sdk.version] = sdk


# Finding installed SDKs isn't cheap, because it goes not only to the
# registry but also to the disk to sanity-check that there is, in fact,
# an SDK installed there and that the registry entry isn't just stale.
# Find this information once, when requested, and cache it.

InstalledSDKList = None
InstalledSDKMap = None

def get_installed_sdks():
    global InstalledSDKList
    global InstalledSDKMap
    debug('get_installed_sdks()')
    if InstalledSDKList is None:
        InstalledSDKList = []
        InstalledSDKMap = {}
        for sdk in SupportedSDKList:
            debug('trying to find SDK %s', sdk.version)
            if sdk.get_sdk_dir():
                debug('found SDK %s', sdk.version)
                InstalledSDKList.append(sdk)
                InstalledSDKMap[sdk.version] = sdk
    return InstalledSDKList


# We may be asked to update multiple construction environments with
# SDK information.  When doing this, we check on-disk for whether
# the SDK has 'mfc' and 'atl' subdirectories.  Since going to disk
# is expensive, cache results by directory.

SDKEnvironmentUpdates = {}

def set_sdk_by_directory(env, sdk_dir):
    global SDKEnvironmentUpdates
    debug('set_sdk_by_directory: Using dir:%s', sdk_dir)
    try:
        env_tuple_list = SDKEnvironmentUpdates[sdk_dir]
    except KeyError:
        env_tuple_list = []
        SDKEnvironmentUpdates[sdk_dir] = env_tuple_list

        include_path = os.path.join(sdk_dir, 'include')
        mfc_path = os.path.join(include_path, 'mfc')
        atl_path = os.path.join(include_path, 'atl')

        if os.path.exists(mfc_path):
            env_tuple_list.append(('INCLUDE', mfc_path))
        if os.path.exists(atl_path):
            env_tuple_list.append(('INCLUDE', atl_path))
        env_tuple_list.append(('INCLUDE', include_path))

        env_tuple_list.append(('LIB', os.path.join(sdk_dir, 'lib')))
        env_tuple_list.append(('LIBPATH', os.path.join(sdk_dir, 'lib')))
        env_tuple_list.append(('PATH', os.path.join(sdk_dir, 'bin')))

    for variable, directory in env_tuple_list:
        env.PrependENVPath(variable, directory)

def get_sdk_by_version(mssdk):
    if mssdk not in SupportedSDKMap:
        raise SCons.Errors.UserError("SDK version {} is not supported".format(repr(mssdk)))
    get_installed_sdks()
    return InstalledSDKMap.get(mssdk)

def get_default_sdk():
    """Set up the default Platform/Windows SDK."""
    get_installed_sdks()
    if not InstalledSDKList:
        return None
    return InstalledSDKList[0]

def mssdk_setup_env(env):
    debug('mssdk_setup_env()')
    if 'MSSDK_DIR' in env:
        sdk_dir = env['MSSDK_DIR']
        if sdk_dir is None:
            return
        sdk_dir = env.subst(sdk_dir)
        debug('mssdk_setup_env: Using MSSDK_DIR:%s', sdk_dir)
    elif 'MSSDK_VERSION' in env:
        sdk_version = env['MSSDK_VERSION']
        if sdk_version is None:
            msg = "SDK version is specified as None"
            raise SCons.Errors.UserError(msg)
        sdk_version = env.subst(sdk_version)
        mssdk = get_sdk_by_version(sdk_version)
        if mssdk is None:
            msg = "SDK version %s is not installed" % sdk_version
            raise SCons.Errors.UserError(msg)
        sdk_dir = mssdk.get_sdk_dir()
        debug('mssdk_setup_env: Using MSSDK_VERSION:%s', sdk_dir)
    elif 'MSVS_VERSION' in env:
        msvs_version = env['MSVS_VERSION']
        debug('mssdk_setup_env:Getting MSVS_VERSION from env:%s', msvs_version)
        if msvs_version is None:
            debug('mssdk_setup_env thinks msvs_version is None')
            return
        msvs_version = env.subst(msvs_version)
        from . import vs
        msvs = vs.get_vs_by_version(msvs_version)
        debug('mssdk_setup_env:msvs is :%s', msvs)
        if not msvs:
            debug('mssdk_setup_env: no VS version detected, bailingout:%s', msvs)
            return
        sdk_version = msvs.sdk_version
        debug('msvs.sdk_version is %s', sdk_version)
        if not sdk_version:
            return
        mssdk = get_sdk_by_version(sdk_version)
        if not mssdk:
            mssdk = get_default_sdk()
            if not mssdk:
                return
        sdk_dir = mssdk.get_sdk_dir()
        debug('mssdk_setup_env: Using MSVS_VERSION:%s', sdk_dir)
    else:
        mssdk = get_default_sdk()
        if not mssdk:
            return
        sdk_dir = mssdk.get_sdk_dir()
        debug('mssdk_setup_env: not using any env values. sdk_dir:%s', sdk_dir)

    set_sdk_by_directory(env, sdk_dir)

    #print "No MSVS_VERSION: this is likely to be a bug"

def mssdk_exists(version=None):
    sdks = get_installed_sdks()
    if version is None:
        return len(sdks) > 0
    return version in sdks

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/MSCommon/vc.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""
MS Compilers: Visual C/C++ detection and configuration.

# TODO:
#   * gather all the information from a single vswhere call instead
#     of calling repeatedly (use json format?)
#   * support passing/setting location for vswhere in env.
#   * supported arch for versions: for old versions of batch file without
#     argument, giving bogus argument cannot be detected, so we have to hardcode
#     this here
#   * print warning when msvc version specified but not found
#   * find out why warning do not print
#   * test on 64 bits XP +  VS 2005 (and VS 6 if possible)
#   * SDK
#   * Assembly
"""

import SCons.compat

import subprocess
import os
import platform
from pathlib import Path
from string import digits as string_digits
from subprocess import PIPE
import re
from collections import (
    namedtuple,
    OrderedDict,
)
import enum

import SCons.Util
import SCons.Warnings
from SCons.Tool import find_program_path

from . import common
from .common import CONFIG_CACHE, debug
from .sdk import get_installed_sdks


class VisualCException(Exception):
    pass

class UnsupportedVersion(VisualCException):
    pass

class MSVCUnsupportedHostArch(VisualCException):
    pass

class MSVCUnsupportedTargetArch(VisualCException):
    pass

class MissingConfiguration(VisualCException):
    pass

class NoVersionFound(VisualCException):
    pass

class BatchFileExecutionError(VisualCException):
    pass

class MSVCScriptNotFound(VisualCException):
    pass

class MSVCUseSettingsError(VisualCException):
    pass

class MSVCVersionNotFound(VisualCException):
    pass

class MSVCArgumentError(VisualCException):
    pass

class MSVCInternalError(VisualCException):
    pass

class BatchFileExecutionWarning(SCons.Warnings.WarningOnByDefault):
    pass


class _Dispatcher:

    classrefs = []

    @classmethod
    def register(cls, classref):
        cls.classrefs.append(classref)

    @classmethod
    def reset(cls):
        debug('reset %s', cls.__name__)
        for classref in cls.classrefs:
            for method in ['reset', '_reset']:
                if not hasattr(classref, method) or not callable(getattr(classref, method, None)):
                    continue
                debug('call %s.%s()', classref.__name__, method)
                func = getattr(classref, method)
                func()

    @classmethod
    def verify(cls):
        debug('verify %s', cls.__name__)
        for classref in cls.classrefs:
            for method in ['verify', '_verify']:
                if not hasattr(classref, method) or not callable(getattr(classref, method, None)):
                    continue
                debug('call %s.%s()', classref.__name__, method)
                func = getattr(classref, method)
                func()

class _Config:

    BOOLEAN_SYMBOLS = {}
    BOOLEAN_EXTERNAL = {}

    for bool, symbol_list, symbol_case_list in [
        (False, (False, 0, '0', None, ''), ('False', 'No',  'F', 'N')),
        (True,  (True,  1, '1'),           ('True',  'Yes', 'T', 'Y')),
    ]:
        BOOLEAN_SYMBOLS[bool] = list(symbol_list)
        for symbol in symbol_case_list:
            BOOLEAN_SYMBOLS[bool].extend([symbol, symbol.lower(), symbol.upper()])

        for symbol in BOOLEAN_SYMBOLS[bool]:
            BOOLEAN_EXTERNAL[symbol] = bool

    MSVC_RUNTIME_DEFINITION = namedtuple('MSVCRuntime', [
        'vc_runtime',
        'vc_runtime_numeric',
        'vc_runtime_alias_list',
        'vc_runtime_vsdef_list',
    ])

    MSVC_RUNTIME_DEFINITION_LIST = []

    MSVC_RUNTIME_INTERNAL = {}
    MSVC_RUNTIME_EXTERNAL = {}

    for vc_runtime, vc_runtime_numeric, vc_runtime_alias_list in [
        ('140', 140, ['ucrt']),
        ('120', 120, ['msvcr120']),
        ('110', 110, ['msvcr110']),
        ('100', 100, ['msvcr100']),
        ('90',   90, ['msvcr90']),
        ('80',   80, ['msvcr80']),
        ('71',   71, ['msvcr71']),
        ('70',   70, ['msvcr70']),
        ('60',   60, ['msvcrt']),
    ]:
        vc_runtime_def = MSVC_RUNTIME_DEFINITION(
            vc_runtime = vc_runtime,
            vc_runtime_numeric = vc_runtime_numeric,
            vc_runtime_alias_list = vc_runtime_alias_list,
            vc_runtime_vsdef_list = [],
        )

        MSVC_RUNTIME_DEFINITION_LIST.append(vc_runtime_def)

        MSVC_RUNTIME_INTERNAL[vc_runtime] = vc_runtime_def
        MSVC_RUNTIME_EXTERNAL[vc_runtime] = vc_runtime_def

        for vc_runtime_alias in vc_runtime_alias_list:
            MSVC_RUNTIME_EXTERNAL[vc_runtime_alias] = vc_runtime_def

    MSVC_BUILDTOOLS_DEFINITION = namedtuple('MSVCBuildtools', [
        'vc_buildtools',
        'vc_buildtools_numeric',
        'vc_version',
        'vc_version_numeric',
        'cl_version',
        'cl_version_numeric',
        'vc_runtime_def',
    ])

    MSVC_BUILDTOOLS_DEFINITION_LIST = []

    MSVC_BUILDTOOLS_INTERNAL = {}
    MSVC_BUILDTOOLS_EXTERNAL = {}

    VC_VERSION_MAP = {}

    for vc_buildtools, vc_version, cl_version, vc_runtime in [
        ('v143', '14.3', '19.3', '140'),
        ('v142', '14.2', '19.2', '140'),
        ('v141', '14.1', '19.1', '140'),
        ('v140', '14.0', '19.0', '140'),
        ('v120', '12.0', '18.0', '120'),
        ('v110', '11.0', '17.0', '110'),
        ('v100', '10.0', '16.0', '100'),
        ('v90',   '9.0', '15.0',  '90'),
        ('v80',   '8.0', '14.0',  '80'),
        ('v71',   '7.1', '13.1',  '71'),
        ('v70',   '7.0', '13.0',  '70'),
        ('v60',   '6.0', '12.0',  '60'),
    ]:

        vc_runtime_def = MSVC_RUNTIME_INTERNAL[vc_runtime]

        vc_buildtools_def = MSVC_BUILDTOOLS_DEFINITION(
            vc_buildtools = vc_buildtools,
            vc_buildtools_numeric = int(vc_buildtools[1:]),
            vc_version = vc_version,
            vc_version_numeric = float(vc_version),
            cl_version = cl_version,
            cl_version_numeric = float(cl_version),
            vc_runtime_def = vc_runtime_def,
        )

        MSVC_BUILDTOOLS_DEFINITION_LIST.append(vc_buildtools_def)

        MSVC_BUILDTOOLS_INTERNAL[vc_buildtools] = vc_buildtools_def
        MSVC_BUILDTOOLS_EXTERNAL[vc_buildtools] = vc_buildtools_def
        MSVC_BUILDTOOLS_EXTERNAL[vc_version] = vc_buildtools_def

        VC_VERSION_MAP[vc_version] = vc_buildtools_def

    MSVS_VERSION_INTERNAL = {}
    MSVS_VERSION_EXTERNAL = {}

    MSVC_VERSION_INTERNAL = {}
    MSVC_VERSION_EXTERNAL = {}

    MSVS_VERSION_MAJOR_MAP = {}

    CL_VERSION_MAP = {}

    MSVC_SDK_VERSIONS = set()

    VISUALSTUDIO_DEFINITION = namedtuple('VisualStudioDefinition', [
        'vs_product',
        'vs_product_alias_list',
        'vs_version',
        'vs_version_major',
        'vs_envvar',
        'vs_express',
        'vs_lookup',
        'vc_sdk_versions',
        'vc_ucrt_versions',
        'vc_uwp',
        'vc_buildtools_def',
        'vc_buildtools_all',
    ])

    VISUALSTUDIO_DEFINITION_LIST = []

    VS_PRODUCT_ALIAS = {
        '1998': ['6']
    }

    # vs_envvar: VisualStudioVersion defined in environment for MSVS 2012 and later
    #            MSVS 2010 and earlier cl_version -> vs_def is a 1:1 mapping
    # SDK attached to product or buildtools?
    for vs_product, vs_version, vs_envvar, vs_express, vs_lookup, vc_sdk, vc_ucrt, vc_uwp, vc_buildtools_all in [
        ('2022', '17.0', True,  False, 'vswhere' , ['10.0', '8.1'], ['10'],   'uwp', ['v143', 'v142', 'v141', 'v140']),
        ('2019', '16.0', True,  False, 'vswhere' , ['10.0', '8.1'], ['10'],   'uwp', ['v142', 'v141', 'v140']),
        ('2017', '15.0', True,  True,  'vswhere' , ['10.0', '8.1'], ['10'],   'uwp', ['v141', 'v140']),
        ('2015', '14.0', True,  True,  'registry', ['10.0', '8.1'], ['10'], 'store', ['v140']),
        ('2013', '12.0', True,  True,  'registry',            None,   None,    None, ['v120']),
        ('2012', '11.0', True,  True,  'registry',            None,   None,    None, ['v110']),
        ('2010', '10.0', False, True,  'registry',            None,   None,    None, ['v100']),
        ('2008',  '9.0', False, True,  'registry',            None,   None,    None, ['v90']),
        ('2005',  '8.0', False, True,  'registry',            None,   None,    None, ['v80']),
        ('2003',  '7.1', False, False, 'registry',            None,   None,    None, ['v71']),
        ('2002',  '7.0', False, False, 'registry',            None,   None,    None, ['v70']),
        ('1998',  '6.0', False, False, 'registry',            None,   None,    None, ['v60']),
    ]:

        vs_version_major = vs_version.split('.')[0]

        vc_buildtools_def = MSVC_BUILDTOOLS_INTERNAL[vc_buildtools_all[0]]

        vs_def = VISUALSTUDIO_DEFINITION(
            vs_product = vs_product,
            vs_product_alias_list = [],
            vs_version = vs_version,
            vs_version_major = vs_version_major,
            vs_envvar = vs_envvar,
            vs_express = vs_express,
            vs_lookup = vs_lookup,
            vc_sdk_versions = vc_sdk,
            vc_ucrt_versions = vc_ucrt,
            vc_uwp = vc_uwp,
            vc_buildtools_def = vc_buildtools_def,
            vc_buildtools_all = vc_buildtools_all,
        )

        VISUALSTUDIO_DEFINITION_LIST.append(vs_def)

        vc_buildtools_def.vc_runtime_def.vc_runtime_vsdef_list.append(vs_def)

        MSVS_VERSION_INTERNAL[vs_product] = vs_def
        MSVS_VERSION_EXTERNAL[vs_product] = vs_def
        MSVS_VERSION_EXTERNAL[vs_version] = vs_def

        MSVC_VERSION_INTERNAL[vc_buildtools_def.vc_version] = vs_def
        MSVC_VERSION_EXTERNAL[vs_product] = vs_def
        MSVC_VERSION_EXTERNAL[vc_buildtools_def.vc_version] = vs_def
        MSVC_VERSION_EXTERNAL[vc_buildtools_def.vc_buildtools] = vs_def

        if vs_product in VS_PRODUCT_ALIAS:
            for vs_product_alias in VS_PRODUCT_ALIAS[vs_product]:
                vs_def.vs_product_alias_list.append(vs_product_alias)
                MSVS_VERSION_EXTERNAL[vs_product_alias] = vs_def
                MSVC_VERSION_EXTERNAL[vs_product_alias] = vs_def

        MSVS_VERSION_MAJOR_MAP[vs_version_major] = vs_def

        CL_VERSION_MAP[vc_buildtools_def.cl_version] = vs_def

        if not vc_sdk:
            continue

        MSVC_SDK_VERSIONS.update(vc_sdk)

    # convert string version set to string version list ranked in descending order
    MSVC_SDK_VERSIONS = [str(f) for f in sorted([float(s) for s in MSVC_SDK_VERSIONS], reverse=True)]

    MSVS_VERSION_LEGACY = {}
    MSVC_VERSION_LEGACY = {}

    for vdict in (MSVS_VERSION_EXTERNAL, MSVC_VERSION_INTERNAL):
        for key, vs_def in vdict.items():
            if key not in MSVS_VERSION_LEGACY:
                MSVS_VERSION_LEGACY[key] = vs_def
                MSVC_VERSION_LEGACY[key] = vs_def

# MSVC_NOTFOUND_POLICY definition:
#     error:   raise exception
#     warning: issue warning and continue
#     ignore:  continue

_MSVC_NOTFOUND_POLICY_DEFINITION = namedtuple('MSVCNotFoundPolicyDefinition', [
    'value',
    'symbol',
])

_MSVC_NOTFOUND_POLICY_INTERNAL = {}
_MSVC_NOTFOUND_POLICY_EXTERNAL = {}

for policy_value, policy_symbol_list in [
    (True,  ['Error',   'Exception']),
    (False, ['Warning', 'Warn']),
    (None,  ['Ignore',  'Suppress']),
]:

    policy_symbol = policy_symbol_list[0].lower()
    policy_def = _MSVC_NOTFOUND_POLICY_DEFINITION(policy_value, policy_symbol)

    _MSVC_NOTFOUND_POLICY_INTERNAL[policy_symbol] = policy_def

    for policy_symbol in policy_symbol_list:
        _MSVC_NOTFOUND_POLICY_EXTERNAL[policy_symbol.lower()] = policy_def
        _MSVC_NOTFOUND_POLICY_EXTERNAL[policy_symbol] = policy_def
        _MSVC_NOTFOUND_POLICY_EXTERNAL[policy_symbol.upper()] = policy_def

_MSVC_NOTFOUND_POLICY_DEF = _MSVC_NOTFOUND_POLICY_INTERNAL['warning']

# Dict to 'canonalize' the arch
_ARCH_TO_CANONICAL = {
    "amd64"     : "amd64",
    "emt64"     : "amd64",
    "i386"      : "x86",
    "i486"      : "x86",
    "i586"      : "x86",
    "i686"      : "x86",
    "ia64"      : "ia64",      # deprecated
    "itanium"   : "ia64",      # deprecated
    "x86"       : "x86",
    "x86_64"    : "amd64",
    "arm"       : "arm",
    "arm64"     : "arm64",
    "aarch64"   : "arm64",
}

# The msvc batch files report errors via stdout.  The following
# regular expression attempts to match known msvc error messages
# written to stdout.
re_script_output_error = re.compile(
    r'^(' + r'|'.join([
        r'VSINSTALLDIR variable is not set',             # 2002-2003
        r'The specified configuration type is missing',  # 2005+
        r'Error in script usage',                        # 2005+
        r'ERROR\:',                                      # 2005+
        r'\!ERROR\!',                                    # 2015-2015
        r'\[ERROR\:',                                    # 2017+
        r'\[ERROR\]',                                    # 2017+
        r'Syntax\:',                                     # 2017+
    ]) + r')'
)

# Lists of compatible host/target combinations are derived from a set of defined
# constant data structures for each host architecture. The derived data structures
# implicitly handle the differences in full versions and express versions of visual
# studio. The host/target combination search lists are contructed in order of
# preference. The construction of the derived data structures is independent of actual
# visual studio installations.  The host/target configurations are used in both the
# initial msvc detection and when finding a valid batch file for a given host/target
# combination.
#
# HostTargetConfig description:
#
#     label:
#         Name used for identification.
#
#     host_all_hosts:
#         Defined list of compatible architectures for each host architecture.
#
#     host_all_targets:
#         Defined list of target architectures for each host architecture.
#
#     host_def_targets:
#         Defined list of default target architectures for each host architecture.
#
#     all_pairs:
#         Derived list of all host/target combination tuples.
#
#     host_target_map:
#         Derived list of all compatible host/target combinations for each
#         supported host/target combination.
#
#     host_all_targets_map:
#         Derived list of all compatible host/target combinations for each
#         supported host.  This is used in the initial check that cl.exe exists
#         in the requisite visual studio vc host/target directory for a given host.
#
#     host_def_targets_map:
#         Derived list of default compatible host/target combinations for each
#         supported host.  This is used for a given host when the user does not
#         request a target archicture.
#
#     target_host_map:
#         Derived list of compatible host/target combinations for each supported
#         target/host combination.  This is used for a given host and target when
#         the user requests a target architecture.

_HOST_TARGET_CONFIG_NT = namedtuple("HostTargetConfig", [
    # defined
    "label",                # name for debugging/output
    "host_all_hosts",       # host_all_hosts[host] -> host_list
    "host_all_targets",     # host_all_targets[host] -> target_list
    "host_def_targets",     # host_def_targets[host] -> target_list
    # derived
    "all_pairs",            # host_target_list
    "host_target_map",      # host_target_map[host][target] -> host_target_list
    "host_all_targets_map", # host_all_targets_map[host][target] -> host_target_list
    "host_def_targets_map", # host_def_targets_map[host][target] -> host_target_list
    "target_host_map",      # target_host_map[target][host] -> host_target_list
])

def _host_target_config_factory(*, label, host_all_hosts, host_all_targets, host_def_targets):

    def _make_host_target_map(all_hosts, all_targets):
        # host_target_map[host][target] -> host_target_list
        host_target_map = {}
        for host, host_list in all_hosts.items():
            host_target_map[host] = {}
            for host_platform in host_list:
                for target_platform in all_targets[host_platform]:
                    if target_platform not in host_target_map[host]:
                        host_target_map[host][target_platform] = []
                    host_target_map[host][target_platform].append((host_platform, target_platform))
        return host_target_map

    def _make_host_all_targets_map(all_hosts, host_target_map, all_targets):
        # host_all_target_map[host] -> host_target_list
        # special host key '_all_' contains all (host,target) combinations
        all = '_all_'
        host_all_targets_map = {}
        host_all_targets_map[all] = []
        for host, host_list in all_hosts.items():
            host_all_targets_map[host] = []
            for host_platform in host_list:
                # all_targets[host_platform]: all targets for compatible host
                for target in all_targets[host_platform]:
                    for host_target in host_target_map[host_platform][target]:
                        for host_key in (host, all):
                            if host_target not in host_all_targets_map[host_key]:
                                host_all_targets_map[host_key].append(host_target)
        return host_all_targets_map

    def _make_host_def_targets_map(all_hosts, host_target_map, def_targets):
        # host_def_targets_map[host] -> host_target_list
        host_def_targets_map = {}
        for host, host_list in all_hosts.items():
            host_def_targets_map[host] = []
            for host_platform in host_list:
                # def_targets[host]: default targets for true host
                for target in def_targets[host]:
                    for host_target in host_target_map[host_platform][target]:
                        if host_target not in host_def_targets_map[host]:
                            host_def_targets_map[host].append(host_target)
        return host_def_targets_map

    def _make_target_host_map(all_hosts, host_all_targets_map):
        # target_host_map[target][host] -> host_target_list
        target_host_map = {}
        for host_platform in all_hosts.keys():
            for host_target in host_all_targets_map[host_platform]:
                _, target = host_target
                if target not in target_host_map:
                    target_host_map[target] = {}
                if host_platform not in target_host_map[target]:
                    target_host_map[target][host_platform] = []
                if host_target not in target_host_map[target][host_platform]:
                    target_host_map[target][host_platform].append(host_target)
        return target_host_map

    host_target_map = _make_host_target_map(host_all_hosts, host_all_targets)
    host_all_targets_map = _make_host_all_targets_map(host_all_hosts, host_target_map, host_all_targets)
    host_def_targets_map = _make_host_def_targets_map(host_all_hosts, host_target_map, host_def_targets)
    target_host_map = _make_target_host_map(host_all_hosts, host_all_targets_map)

    all_pairs = host_all_targets_map['_all_']
    del host_all_targets_map['_all_']

    host_target_cfg = _HOST_TARGET_CONFIG_NT(
        label = label,
        host_all_hosts = dict(host_all_hosts),
        host_all_targets = host_all_targets,
        host_def_targets = host_def_targets,
        all_pairs = all_pairs,
        host_target_map = host_target_map,
        host_all_targets_map = host_all_targets_map,
        host_def_targets_map = host_def_targets_map,
        target_host_map = target_host_map,
    )

    return host_target_cfg

# 14.1 (VS2017) and later

# Given a (host, target) tuple, return a tuple containing the batch file to
# look for and a tuple of path components to find cl.exe. We can't rely on returning
# an arg to use for vcvarsall.bat, because that script will run even if given
# a host/target pair that isn't installed.
#
# Starting with 14.1 (VS2017), the batch files are located in directory
# <VSROOT>/VC/Auxiliary/Build.  The batch file name is the first value of the
# stored tuple.
#
# The build tools are organized by host and target subdirectories under each toolset
# version directory.  For example,  <VSROOT>/VC/Tools/MSVC/14.31.31103/bin/Hostx64/x64.
# The cl path fragment under the toolset version folder is the second value of
# the stored tuple.

_GE2017_HOST_TARGET_BATCHFILE_CLPATHCOMPS = {

    ('amd64', 'amd64') : ('vcvars64.bat',          ('bin', 'Hostx64', 'x64')),
    ('amd64', 'x86')   : ('vcvarsamd64_x86.bat',   ('bin', 'Hostx64', 'x86')),
    ('amd64', 'arm')   : ('vcvarsamd64_arm.bat',   ('bin', 'Hostx64', 'arm')),
    ('amd64', 'arm64') : ('vcvarsamd64_arm64.bat', ('bin', 'Hostx64', 'arm64')),

    ('x86',   'amd64') : ('vcvarsx86_amd64.bat',   ('bin', 'Hostx86', 'x64')),
    ('x86',   'x86')   : ('vcvars32.bat',          ('bin', 'Hostx86', 'x86')),
    ('x86',   'arm')   : ('vcvarsx86_arm.bat',     ('bin', 'Hostx86', 'arm')),
    ('x86',   'arm64') : ('vcvarsx86_arm64.bat',   ('bin', 'Hostx86', 'arm64')),

}

_GE2017_HOST_TARGET_CFG = _host_target_config_factory(

    label = 'GE2017',

    host_all_hosts = OrderedDict([
        ('amd64', ['amd64', 'x86']),
        ('x86',   ['x86']),
        ('arm64', ['amd64', 'x86']),
        ('arm',   ['x86']),
    ]),

    host_all_targets = {
        'amd64': ['amd64', 'x86', 'arm64', 'arm'],
        'x86':   ['x86', 'amd64', 'arm', 'arm64'],
        'arm64': [],
        'arm':   [],
    },

    host_def_targets = {
        'amd64': ['amd64', 'x86'],
        'x86':   ['x86'],
        'arm64': ['arm64', 'arm'],
        'arm':   ['arm'],
    },

)

# debug("_GE2017_HOST_TARGET_CFG: %s", _GE2017_HOST_TARGET_CFG)

# 14.0 (VS2015) to 8.0 (VS2005)

# Given a (host, target) tuple, return a tuple containing the argument for
# the batch file and a tuple of the path components to find cl.exe.
#
# In 14.0 (VS2015) and earlier, the original x86 tools are in the tools
# bin directory (i.e., <VSROOT>/VC/bin).  Any other tools are in subdirectory
# named for the the host/target pair or a single name if the host==target.

_LE2015_HOST_TARGET_BATCHARG_CLPATHCOMPS = {

    ('amd64', 'amd64') : ('amd64',     ('bin', 'amd64')),
    ('amd64', 'x86')   : ('amd64_x86', ('bin', 'amd64_x86')),
    ('amd64', 'arm')   : ('amd64_arm', ('bin', 'amd64_arm')),

    ('x86',   'amd64') : ('x86_amd64', ('bin', 'x86_amd64')),
    ('x86',   'x86')   : ('x86',       ('bin', )),
    ('x86',   'arm')   : ('x86_arm',   ('bin', 'x86_arm')),
    ('x86',   'ia64')  : ('x86_ia64',  ('bin', 'x86_ia64')),

    ('arm',   'arm')   : ('arm',       ('bin', 'arm')),
    ('ia64',  'ia64')  : ('ia64',      ('bin', 'ia64')),

}

_LE2015_HOST_TARGET_CFG = _host_target_config_factory(

    label = 'LE2015',

    host_all_hosts = OrderedDict([
        ('amd64', ['amd64', 'x86']),
        ('x86',   ['x86']),
        ('arm',   ['arm']),
        ('ia64',  ['ia64']),
    ]),

    host_all_targets = {
        'amd64': ['amd64', 'x86', 'arm'],
        'x86':   ['x86', 'amd64', 'arm', 'ia64'],
        'arm':   ['arm'],
        'ia64':  ['ia64'],
    },

    host_def_targets = {
        'amd64': ['amd64', 'x86'],
        'x86':   ['x86'],
        'arm':   ['arm'],
        'ia64':  ['ia64'],
    },

)

# debug("_LE2015_HOST_TARGET_CFG: %s", _LE2015_HOST_TARGET_CFG)

# 7.1 (VS2003) and earlier

# For 7.1 (VS2003) and earlier, there are only x86 targets and the batch files
# take no arguments.

_LE2003_HOST_TARGET_CFG = _host_target_config_factory(

    label = 'LE2003',

    host_all_hosts = OrderedDict([
        ('amd64', ['x86']),
        ('x86',   ['x86']),
    ]),

    host_all_targets = {
        'amd64': ['x86'],
        'x86':   ['x86'],
    },

    host_def_targets = {
        'amd64': ['x86'],
        'x86':   ['x86'],
    },

)

# debug("_LE2003_HOST_TARGET_CFG: %s", _LE2003_HOST_TARGET_CFG)

_CL_EXE_NAME = 'cl.exe'

def get_msvc_version_numeric(msvc_version):
    """Get the raw version numbers from a MSVC_VERSION string, so it
    could be cast to float or other numeric values. For example, '14.0Exp'
    would get converted to '14.0'.

    Args:
        msvc_version: str
            string representing the version number, could contain non
            digit characters

    Returns:
        str: the value converted to a numeric only string

    """
    return ''.join([x for  x in msvc_version if x in string_digits + '.'])

def get_host_platform(host_platform):

    host_platform = host_platform.lower()

    # Solaris returns i86pc for both 32 and 64 bit architectures
    if host_platform == 'i86pc':
        if platform.architecture()[0] == "64bit":
            host_platform = "amd64"
        else:
            host_platform = "x86"

    try:
        host =_ARCH_TO_CANONICAL[host_platform]
    except KeyError:
        msg = "Unrecognized host architecture %s"
        raise MSVCUnsupportedHostArch(msg % repr(host_platform)) from None

    return host

_native_host_platform = None

def get_native_host_platform():
    global _native_host_platform

    if _native_host_platform is None:

        _native_host_platform = get_host_platform(platform.machine())

    return _native_host_platform

def get_host_target(env, msvc_version, all_host_targets=False):

    vernum = float(get_msvc_version_numeric(msvc_version))

    if vernum > 14:
        # 14.1 (VS2017) and later
        host_target_cfg = _GE2017_HOST_TARGET_CFG
    elif 14 >= vernum >= 8:
        # 14.0 (VS2015) to 8.0 (VS2005)
        host_target_cfg = _LE2015_HOST_TARGET_CFG
    else:
        # 7.1 (VS2003) and earlier
        host_target_cfg = _LE2003_HOST_TARGET_CFG

    host_arch = env.get('HOST_ARCH') if env else None
    debug("HOST_ARCH:%s", str(host_arch))

    if host_arch:
        host_platform = get_host_platform(host_arch)
    else:
        host_platform = get_native_host_platform()

    target_arch = env.get('TARGET_ARCH') if env else None
    debug("TARGET_ARCH:%s", str(target_arch))

    if target_arch:

        try:
            target_platform = _ARCH_TO_CANONICAL[target_arch.lower()]
        except KeyError:
            all_archs = str(list(_ARCH_TO_CANONICAL.keys()))
            raise MSVCUnsupportedTargetArch(
                "Unrecognized target architecture %s\n\tValid architectures: %s"
                % (repr(target_arch), all_archs)
            ) from None

        target_host_map = host_target_cfg.target_host_map

        try:
            host_target_list = target_host_map[target_platform][host_platform]
        except KeyError:
            host_target_list = []
            warn_msg = "unsupported host, target combination ({}, {}) for MSVC version {}".format(
                repr(host_platform), repr(target_platform), msvc_version
            )
            SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg)
            debug(warn_msg)

    else:

        target_platform = None

        if all_host_targets:
            host_targets_map = host_target_cfg.host_all_targets_map
        else:
            host_targets_map = host_target_cfg.host_def_targets_map

        try:
            host_target_list = host_targets_map[host_platform]
        except KeyError:
            msg = "Unrecognized host architecture %s for version %s"
            raise MSVCUnsupportedHostArch(msg % (repr(host_platform), msvc_version)) from None

    return (host_platform, target_platform, host_target_list)

# If you update this, update SupportedVSList in Tool/MSCommon/vs.py, and the
# MSVC_VERSION documentation in Tool/msvc.xml.
_VCVER = [
    "14.3",
    "14.2",
    "14.1", "14.1Exp",
    "14.0", "14.0Exp",
    "12.0", "12.0Exp",
    "11.0", "11.0Exp",
    "10.0", "10.0Exp",
    "9.0", "9.0Exp",
    "8.0", "8.0Exp",
    "7.1",
    "7.0",
    "6.0"]

# if using vswhere, configure command line arguments to probe for installed VC editions
_VCVER_TO_VSWHERE_VER = {
    '14.3': [
        ["-version", "[17.0, 18.0)"],  # default: Enterprise, Professional, Community  (order unpredictable?)
        ["-version", "[17.0, 18.0)", "-products", "Microsoft.VisualStudio.Product.BuildTools"],  # BuildTools
    ],
    '14.2': [
        ["-version", "[16.0, 17.0)"],  # default: Enterprise, Professional, Community  (order unpredictable?)
        ["-version", "[16.0, 17.0)", "-products", "Microsoft.VisualStudio.Product.BuildTools"],  # BuildTools
    ],
    '14.1': [
        ["-version", "[15.0, 16.0)"],  # default: Enterprise, Professional, Community (order unpredictable?)
        ["-version", "[15.0, 16.0)", "-products", "Microsoft.VisualStudio.Product.BuildTools"],  # BuildTools
    ],
    '14.1Exp': [
        ["-version", "[15.0, 16.0)", "-products", "Microsoft.VisualStudio.Product.WDExpress"],  # Express
    ],
}

_VCVER_TO_PRODUCT_DIR = {
    '14.3': [
        (SCons.Util.HKEY_LOCAL_MACHINE, r'')],  # not set by this version
    '14.2': [
        (SCons.Util.HKEY_LOCAL_MACHINE, r'')],  # not set by this version
    '14.1': [
        (SCons.Util.HKEY_LOCAL_MACHINE, r'')],  # not set by this version
    '14.1Exp': [
        (SCons.Util.HKEY_LOCAL_MACHINE, r'')],  # not set by this version
    '14.0': [
        (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\14.0\Setup\VC\ProductDir')],
    '14.0Exp': [
        (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\14.0\Setup\VC\ProductDir')],
    '12.0': [
        (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\12.0\Setup\VC\ProductDir'),
    ],
    '12.0Exp': [
        (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\12.0\Setup\VC\ProductDir'),
    ],
    '11.0': [
        (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\11.0\Setup\VC\ProductDir'),
    ],
    '11.0Exp': [
        (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\11.0\Setup\VC\ProductDir'),
    ],
    '10.0': [
        (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\10.0\Setup\VC\ProductDir'),
    ],
    '10.0Exp': [
        (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\10.0\Setup\VC\ProductDir'),
    ],
    '9.0': [
        (SCons.Util.HKEY_CURRENT_USER, r'Microsoft\DevDiv\VCForPython\9.0\installdir',),
        (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\9.0\Setup\VC\ProductDir',),
    ],
    '9.0Exp': [
        (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\9.0\Setup\VC\ProductDir'),
    ],
    '8.0': [
        (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\8.0\Setup\VC\ProductDir'),
    ],
    '8.0Exp': [
        (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VCExpress\8.0\Setup\VC\ProductDir'),
    ],
    '7.1': [
        (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\7.1\Setup\VC\ProductDir'),
    ],
    '7.0': [
        (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\7.0\Setup\VC\ProductDir'),
    ],
    '6.0': [
        (SCons.Util.HKEY_LOCAL_MACHINE, r'Microsoft\VisualStudio\6.0\Setup\Microsoft Visual C++\ProductDir'),
    ]
}


def msvc_version_to_maj_min(msvc_version):
    msvc_version_numeric = get_msvc_version_numeric(msvc_version)

    t = msvc_version_numeric.split(".")
    if not len(t) == 2:
        raise ValueError("Unrecognized version %s (%s)" % (msvc_version,msvc_version_numeric))
    try:
        maj = int(t[0])
        min = int(t[1])
        return maj, min
    except ValueError as e:
        raise ValueError("Unrecognized version %s (%s)" % (msvc_version,msvc_version_numeric)) from None


VSWHERE_PATHS = [os.path.join(p,'vswhere.exe') for p in  [
    os.path.expandvars(r"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer"),
    os.path.expandvars(r"%ProgramFiles%\Microsoft Visual Studio\Installer"),
    os.path.expandvars(r"%ChocolateyInstall%\bin"),
]]

def msvc_find_vswhere():
    """ Find the location of vswhere """
    # For bug 3333: support default location of vswhere for both
    # 64 and 32 bit windows installs.
    # For bug 3542: also accommodate not being on C: drive.
    # NB: this gets called from testsuite on non-Windows platforms.
    # Whether that makes sense or not, don't break it for those.
    vswhere_path = None
    for pf in VSWHERE_PATHS:
        if os.path.exists(pf):
            vswhere_path = pf
            break

    return vswhere_path

def find_vc_pdir_vswhere(msvc_version, env=None):
    """ Find the MSVC product directory using the vswhere program.

    Args:
        msvc_version: MSVC version to search for
        env: optional to look up VSWHERE variable

    Returns:
        MSVC install dir or None

    Raises:
        UnsupportedVersion: if the version is not known by this file

    """
    try:
        vswhere_version = _VCVER_TO_VSWHERE_VER[msvc_version]
    except KeyError:
        debug("Unknown version of MSVC: %s", msvc_version)
        raise UnsupportedVersion("Unknown version %s" % msvc_version) from None

    if env is None or not env.get('VSWHERE'):
        vswhere_path = msvc_find_vswhere()
    else:
        vswhere_path = env.subst('$VSWHERE')

    if vswhere_path is None:
        return None

    debug('VSWHERE: %s', vswhere_path)
    for vswhere_version_args in vswhere_version:

        vswhere_cmd = [vswhere_path] + vswhere_version_args + ["-property", "installationPath"]

        debug("running: %s", vswhere_cmd)

        #cp = subprocess.run(vswhere_cmd, capture_output=True, check=True)  # 3.7+ only
        cp = subprocess.run(vswhere_cmd, stdout=PIPE, stderr=PIPE, check=True)

        if cp.stdout:
            # vswhere could return multiple lines, e.g. if Build Tools
            # and {Community,Professional,Enterprise} are both installed.
            # We could define a way to pick the one we prefer, but since
            # this data is currently only used to make a check for existence,
            # returning the first hit should be good enough.
            lines = cp.stdout.decode("mbcs").splitlines()
            return os.path.join(lines[0], 'VC')
        else:
            # We found vswhere, but no install info available for this version
            pass

    return None


def find_vc_pdir(env, msvc_version):
    """Find the MSVC product directory for the given version.

    Tries to look up the path using a registry key from the table
    _VCVER_TO_PRODUCT_DIR; if there is no key, calls find_vc_pdir_wshere
    for help instead.

    Args:
        msvc_version: str
            msvc version (major.minor, e.g. 10.0)

    Returns:
        str: Path found in registry, or None

    Raises:
        UnsupportedVersion: if the version is not known by this file.
        MissingConfiguration: found version but the directory is missing.

        Both exceptions inherit from VisualCException.

    """
    root = 'Software\\'
    try:
        hkeys = _VCVER_TO_PRODUCT_DIR[msvc_version]
    except KeyError:
        debug("Unknown version of MSVC: %s", msvc_version)
        raise UnsupportedVersion("Unknown version %s" % msvc_version) from None

    for hkroot, key in hkeys:
        try:
            comps = None
            if not key:
                comps = find_vc_pdir_vswhere(msvc_version, env)
                if not comps:
                    debug('no VC found for version %s', repr(msvc_version))
                    raise OSError
                debug('VC found: %s', repr(msvc_version))
                return comps
            else:
                if common.is_win64():
                    try:
                        # ordinarily at win64, try Wow6432Node first.
                        comps = common.read_reg(root + 'Wow6432Node\\' + key, hkroot)
                    except OSError:
                        # at Microsoft Visual Studio for Python 2.7, value is not in Wow6432Node
                        pass
                if not comps:
                    # not Win64, or Microsoft Visual Studio for Python 2.7
                    comps = common.read_reg(root + key, hkroot)
        except OSError:
            debug('no VC registry key %s', repr(key))
        else:
            debug('found VC in registry: %s', comps)
            if os.path.exists(comps):
                return comps
            else:
                debug('reg says dir is %s, but it does not exist. (ignoring)', comps)
                raise MissingConfiguration("registry dir {} not found on the filesystem".format(comps))
    return None

def find_batch_file(env, msvc_version, host_arch, target_arch):
    """
    Find the location of the batch script which should set up the compiler
    for any TARGET_ARCH whose compilers were installed by Visual Studio/VCExpress

    In newer (2017+) compilers, make use of the fact there are vcvars
    scripts named with a host_target pair that calls vcvarsall.bat properly,
    so use that and return an empty argument.
    """
    pdir = find_vc_pdir(env, msvc_version)
    if pdir is None:
        raise NoVersionFound("No version of Visual Studio found")
    debug('looking in %s', pdir)

    # filter out e.g. "Exp" from the version name
    msvc_ver_numeric = get_msvc_version_numeric(msvc_version)
    vernum = float(msvc_ver_numeric)

    arg = ''
    vcdir = None

    if vernum > 14:
        # 14.1 (VS2017) and later
        batfiledir = os.path.join(pdir, "Auxiliary", "Build")
        batfile, _ = _GE2017_HOST_TARGET_BATCHFILE_CLPATHCOMPS[(host_arch, target_arch)]
        batfilename = os.path.join(batfiledir, batfile)
        vcdir = pdir
    elif 14 >= vernum >= 8:
        # 14.0 (VS2015) to 8.0 (VS2005)
        arg, _ = _LE2015_HOST_TARGET_BATCHARG_CLPATHCOMPS[(host_arch, target_arch)]
        batfilename = os.path.join(pdir, "vcvarsall.bat")
    else:
        # 7.1 (VS2003) and earlier
        pdir = os.path.join(pdir, "Bin")
        batfilename = os.path.join(pdir, "vcvars32.bat")

    if not os.path.exists(batfilename):
        debug("Not found: %s", batfilename)
        batfilename = None

    installed_sdks = get_installed_sdks()
    for _sdk in installed_sdks:
        sdk_bat_file = _sdk.get_sdk_vc_script(host_arch, target_arch)
        if not sdk_bat_file:
            debug("batch file not found:%s", _sdk)
        else:
            sdk_bat_file_path = os.path.join(pdir, sdk_bat_file)
            if os.path.exists(sdk_bat_file_path):
                debug('sdk_bat_file_path:%s', sdk_bat_file_path)
                return (batfilename, arg, vcdir, sdk_bat_file_path)

    return (batfilename, arg, vcdir, None)

__INSTALLED_VCS_RUN = None
_VC_TOOLS_VERSION_FILE_PATH = ['Auxiliary', 'Build', 'Microsoft.VCToolsVersion.default.txt']
_VC_TOOLS_VERSION_FILE = os.sep.join(_VC_TOOLS_VERSION_FILE_PATH)

def _check_cl_exists_in_vc_dir(env, vc_dir, msvc_version):
    """Return status of finding a cl.exe to use.

    Locates cl in the vc_dir depending on TARGET_ARCH, HOST_ARCH and the
    msvc version. TARGET_ARCH and HOST_ARCH can be extracted from the
    passed env, unless the env is None, in which case the native platform is
    assumed for the host and all associated targets.

    Args:
        env: Environment
            a construction environment, usually if this is passed its
            because there is a desired TARGET_ARCH to be used when searching
            for a cl.exe
        vc_dir: str
            the path to the VC dir in the MSVC installation
        msvc_version: str
            msvc version (major.minor, e.g. 10.0)

    Returns:
        bool:

    """

    # Find the host, target, and all candidate (host, target) platform combinations:
    platforms = get_host_target(env, msvc_version, all_host_targets=True)
    debug("host_platform %s, target_platform %s host_target_list %s", *platforms)
    host_platform, target_platform, host_target_list = platforms

    vernum = float(get_msvc_version_numeric(msvc_version))

    # make sure the cl.exe exists meaning the tool is installed
    if vernum > 14:
        # 14.1 (VS2017) and later
        # 2017 and newer allowed multiple versions of the VC toolset to be
        # installed at the same time. This changes the layout.
        # Just get the default tool version for now
        #TODO: support setting a specific minor VC version
        default_toolset_file = os.path.join(vc_dir, _VC_TOOLS_VERSION_FILE)
        try:
            with open(default_toolset_file) as f:
                vc_specific_version = f.readlines()[0].strip()
        except IOError:
            debug('failed to read %s', default_toolset_file)
            return False
        except IndexError:
            debug('failed to find MSVC version in %s', default_toolset_file)
            return False

        for host_platform, target_platform in host_target_list:

            debug('host platform %s, target platform %s for version %s', host_platform, target_platform, msvc_version)

            batchfile_clpathcomps = _GE2017_HOST_TARGET_BATCHFILE_CLPATHCOMPS.get((host_platform, target_platform), None)
            if batchfile_clpathcomps is None:
                debug('unsupported host/target platform combo: (%s,%s)', host_platform, target_platform)
                continue

            _, cl_path_comps = batchfile_clpathcomps
            cl_path = os.path.join(vc_dir, 'Tools', 'MSVC', vc_specific_version, *cl_path_comps, _CL_EXE_NAME)
            debug('checking for %s at %s', _CL_EXE_NAME, cl_path)

            if os.path.exists(cl_path):
                debug('found %s!', _CL_EXE_NAME)
                return True

    elif 14 >= vernum >= 8:
        # 14.0 (VS2015) to 8.0 (VS2005)

        cl_path_prefixes = [None]
        if msvc_version == '9.0':
            # Visual C++ for Python registry key is installdir (root) not productdir (vc)
            cl_path_prefixes.append(('VC',))

        for host_platform, target_platform in host_target_list:

            debug('host platform %s, target platform %s for version %s', host_platform, target_platform, msvc_version)

            batcharg_clpathcomps = _LE2015_HOST_TARGET_BATCHARG_CLPATHCOMPS.get((host_platform, target_platform), None)
            if batcharg_clpathcomps is None:
                debug('unsupported host/target platform combo: (%s,%s)', host_platform, target_platform)
                continue

            _, cl_path_comps = batcharg_clpathcomps
            for cl_path_prefix in cl_path_prefixes:

                cl_path_comps_adj = cl_path_prefix + cl_path_comps if cl_path_prefix else cl_path_comps
                cl_path = os.path.join(vc_dir, *cl_path_comps_adj, _CL_EXE_NAME)
                debug('checking for %s at %s', _CL_EXE_NAME, cl_path)

                if os.path.exists(cl_path):
                    debug('found %s', _CL_EXE_NAME)
                    return True

    elif 8 > vernum >= 6:
        # 7.1 (VS2003) to 6.0 (VS6)

        # quick check for vc_dir/bin and vc_dir/ before walk
        # need to check root as the walk only considers subdirectories
        for cl_dir in ('bin', ''):
            cl_path = os.path.join(vc_dir, cl_dir, _CL_EXE_NAME)
            if os.path.exists(cl_path):
                debug('%s found %s', _CL_EXE_NAME, cl_path)
                return True
        # not in bin or root: must be in a subdirectory
        for cl_root, cl_dirs, _ in os.walk(vc_dir):
            for cl_dir in cl_dirs:
                cl_path = os.path.join(cl_root, cl_dir, _CL_EXE_NAME)
                if os.path.exists(cl_path):
                    debug('%s found %s', _CL_EXE_NAME, cl_path)
                    return True
        return False

    else:
        # version not support return false
        debug('unsupported MSVC version: %s', str(vernum))

    return False

def get_installed_vcs(env=None):
    global __INSTALLED_VCS_RUN

    if __INSTALLED_VCS_RUN is not None:
        return __INSTALLED_VCS_RUN

    installed_versions = []

    for ver in _VCVER:
        debug('trying to find VC %s', ver)
        try:
            VC_DIR = find_vc_pdir(env, ver)
            if VC_DIR:
                debug('found VC %s', ver)
                if _check_cl_exists_in_vc_dir(env, VC_DIR, ver):
                    installed_versions.append(ver)
                else:
                    debug('no compiler found %s', ver)
            else:
                debug('return None for ver %s', ver)
        except (MSVCUnsupportedTargetArch, MSVCUnsupportedHostArch):
            # Allow this exception to propagate further as it should cause
            # SCons to exit with an error code
            raise
        except VisualCException as e:
            debug('did not find VC %s: caught exception %s', ver, str(e))

    __INSTALLED_VCS_RUN = installed_versions
    return __INSTALLED_VCS_RUN

def reset_installed_vcs():
    """Make it try again to find VC.  This is just for the tests."""
    global __INSTALLED_VCS_RUN
    __INSTALLED_VCS_RUN = None
    _Dispatcher.reset()

def get_default_installed_msvc(env=None):
    vcs = get_installed_vcs(env)
    msvc_version = vcs[0] if vcs else None
    debug('msvc_version=%s', repr(msvc_version))
    return msvc_version

# Running these batch files isn't cheap: most of the time spent in
# msvs.generate() is due to vcvars*.bat.  In a build that uses "tools='msvs'"
# in multiple environments, for example:
#    env1 = Environment(tools='msvs')
#    env2 = Environment(tools='msvs')
# we can greatly improve the speed of the second and subsequent Environment
# (or Clone) calls by memoizing the environment variables set by vcvars*.bat.
#
# Updated: by 2018, vcvarsall.bat had gotten so expensive (vs2017 era)
# it was breaking CI builds because the test suite starts scons so many
# times and the existing memo logic only helped with repeated calls
# within the same scons run. Windows builds on the CI system were split
# into chunks to get around single-build time limits.
# With VS2019 it got even slower and an optional persistent cache file
# was introduced. The cache now also stores only the parsed vars,
# not the entire output of running the batch file - saves a bit
# of time not parsing every time.

script_env_cache = None

def script_env(script, args=None):
    global script_env_cache

    if script_env_cache is None:
        script_env_cache = common.read_script_env_cache()
    cache_key = (script, args if args else None)
    cache_data = script_env_cache.get(cache_key, None)

    # Brief sanity check: if we got a value for the key,
    # see if it has a VCToolsInstallDir entry that is not empty.
    # If so, and that path does not exist, invalidate the entry.
    # If empty, this is an old compiler, just leave it alone.
    if cache_data is not None:
        try:
            toolsdir = cache_data["VCToolsInstallDir"]
        except KeyError:
            # we write this value, so should not happen
            pass
        else:
            if toolsdir:
                toolpath = Path(toolsdir[0])
                if not toolpath.exists():
                    cache_data = None

    if cache_data is None:
        stdout = common.get_output(script, args)
        cache_data = common.parse_output(stdout)

        olines = stdout.splitlines()
        #debug(olines)

        # process stdout: batch file errors (not necessarily first line)
        script_errlog = []
        for line in olines:
            if re_script_output_error.match(line):
                if not script_errlog:
                    script_errlog.append('vc script errors detected:')
                script_errlog.append(line)

        if script_errlog:
            script_errmsg = '\n'.join(script_errlog)
            have_cl = False
            if cache_data and 'PATH' in cache_data:
                for p in cache_data['PATH']:
                    if os.path.exists(os.path.join(p, _CL_EXE_NAME)):
                        have_cl = True
                        break
            if not have_cl:
                # detected errors, cl.exe not on path
                raise BatchFileExecutionError(script_errmsg)
            else:
                # detected errors, cl.exe on path
                debug('script=%s args=%s errors=%s', repr(script), repr(args), script_errmsg)
                # This may be a bad idea (scons environment != vs cmdline environment)
                SCons.Warnings.warn(BatchFileExecutionWarning, script_errmsg)
                # TODO: errlog/errstr should be added to cache and warning moved to call site

        # once we updated cache, give a chance to write out if user wanted
        script_env_cache[cache_key] = cache_data
        common.write_script_env_cache(script_env_cache)

    return cache_data

def _msvc_notfound_policy_lookup(symbol):

    try:
        notfound_policy_def = _MSVC_NOTFOUND_POLICY_EXTERNAL[symbol]
    except KeyError:
        err_msg = "Value specified for MSVC_NOTFOUND_POLICY is not supported: {}.\n" \
                  "  Valid values are: {}".format(
                     repr(symbol),
                     ', '.join([repr(s) for s in _MSVC_NOTFOUND_POLICY_EXTERNAL.keys()])
                  )
        raise ValueError(err_msg)

    return notfound_policy_def

def set_msvc_notfound_policy(MSVC_NOTFOUND_POLICY=None):
    """ Set the default policy when MSVC is not found.

    Args:
        MSVC_NOTFOUND_POLICY:
           string representing the policy behavior
           when MSVC is not found or None

    Returns:
        The previous policy is returned when the MSVC_NOTFOUND_POLICY argument
        is not None. The active policy is returned when the MSVC_NOTFOUND_POLICY
        argument is None.

    """
    global _MSVC_NOTFOUND_POLICY_DEF

    prev_policy = _MSVC_NOTFOUND_POLICY_DEF.symbol

    policy = MSVC_NOTFOUND_POLICY
    if policy is not None:
        _MSVC_NOTFOUND_POLICY_DEF = _msvc_notfound_policy_lookup(policy)

    debug(
        'prev_policy=%s, set_policy=%s, policy.symbol=%s, policy.value=%s',
        repr(prev_policy), repr(policy),
        repr(_MSVC_NOTFOUND_POLICY_DEF.symbol), repr(_MSVC_NOTFOUND_POLICY_DEF.value)
    )

    return prev_policy

def get_msvc_notfound_policy():
    """Return the active policy when MSVC is not found."""
    debug(
        'policy.symbol=%s, policy.value=%s',
        repr(_MSVC_NOTFOUND_POLICY_DEF.symbol), repr(_MSVC_NOTFOUND_POLICY_DEF.value)
    )
    return _MSVC_NOTFOUND_POLICY_DEF.symbol

def _msvc_notfound_policy_handler(env, msg):

    if env and 'MSVC_NOTFOUND_POLICY' in env:
        # environment setting
        notfound_policy_src = 'environment'
        policy = env['MSVC_NOTFOUND_POLICY']
        if policy is not None:
            # user policy request
            notfound_policy_def = _msvc_notfound_policy_lookup(policy)
        else:
            # active global setting
            notfound_policy_def = _MSVC_NOTFOUND_POLICY_DEF
    else:
        # active global setting
        notfound_policy_src = 'default'
        policy = None
        notfound_policy_def = _MSVC_NOTFOUND_POLICY_DEF

    debug(
        'source=%s, set_policy=%s, policy.symbol=%s, policy.value=%s',
        notfound_policy_src, repr(policy), repr(notfound_policy_def.symbol), repr(notfound_policy_def.value)
    )

    if notfound_policy_def.value is None:
        # ignore
        pass
    elif notfound_policy_def.value:
        raise MSVCVersionNotFound(msg)
    else:
        SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, msg)

class _MSVCSetupEnvDefault:
    """
    Determine if and/or when an error/warning should be issued when there
    are no versions of msvc installed.  If there is at least one version of
    msvc installed, these routines do (almost) nothing.

    Notes:
        * When msvc is the default compiler because there are no compilers
          installed, a build may fail due to the cl.exe command not being
          recognized.  Currently, there is no easy way to detect during
          msvc initialization if the default environment will be used later
          to build a program and/or library. There is no error/warning
          as there are legitimate SCons uses that do not require a c compiler.
        * As implemented, the default is that a warning is issued.  This can
          be changed globally via the function set_msvc_notfound_policy and/or
          through the environment via the MSVC_NOTFOUND_POLICY variable.
    """

    separator = r';'

    need_init = True

    @classmethod
    def reset(cls):
        debug('msvc default:init')
        cls.n_setup = 0                 # number of calls to msvc_setup_env_once
        cls.default_ismsvc = False      # is msvc the default compiler
        cls.default_tools_re_list = []  # list of default tools regular expressions
        cls.msvc_tools_init = set()     # tools registered via msvc_exists
        cls.msvc_tools = None           # tools registered via msvc_setup_env_once
        cls.msvc_installed = False      # is msvc installed (vcs_installed > 0)
        cls.msvc_nodefault = False      # is there a default version of msvc
        cls.need_init = True            # reset initialization indicator

    @classmethod
    def _initialize(cls, env):
        if cls.need_init:
            cls.reset()
            cls.need_init = False
            vcs = get_installed_vcs(env)
            cls.msvc_installed = len(vcs) > 0
            debug('msvc default:msvc_installed=%s', cls.msvc_installed)

    @classmethod
    def register_tool(cls, env, tool):
        debug('msvc default:tool=%s', tool)
        if cls.need_init:
            cls._initialize(env)
        if cls.msvc_installed:
            return None
        if not tool:
            return None
        if cls.n_setup == 0:
            if tool not in cls.msvc_tools_init:
                cls.msvc_tools_init.add(tool)
                debug('msvc default:tool=%s, msvc_tools_init=%s', tool, cls.msvc_tools_init)
            return None
        if tool not in cls.msvc_tools:
            cls.msvc_tools.add(tool)
            debug('msvc default:tool=%s, msvc_tools=%s', tool, cls.msvc_tools)

    @classmethod
    def register_setup(cls, env):
        debug('msvc default')
        if cls.need_init:
            cls._initialize(env)
        cls.n_setup += 1
        if not cls.msvc_installed:
            cls.msvc_tools = set(cls.msvc_tools_init)
            if cls.n_setup == 1:
                tool_list = env.get('TOOLS', None)
                if tool_list and tool_list[0] == 'default':
                    if len(tool_list) > 1 and tool_list[1] in cls.msvc_tools:
                        # msvc tools are the default compiler
                        cls.default_ismsvc = True
            cls.msvc_nodefault = False
            debug(
                'msvc default:n_setup=%d, msvc_installed=%s, default_ismsvc=%s',
                cls.n_setup, cls.msvc_installed, cls.default_ismsvc
            )

    @classmethod
    def set_nodefault(cls):
        # default msvc version, msvc not installed
        cls.msvc_nodefault = True
        debug('msvc default:msvc_nodefault=%s', cls.msvc_nodefault)

    @classmethod
    def register_iserror(cls, env, tool):

        cls.register_tool(env, tool)

        if cls.msvc_installed:
            # msvc installed
            return None

        if not cls.msvc_nodefault:
            # msvc version specified
            return None

        tool_list = env.get('TOOLS', None)
        if not tool_list:
            # tool list is empty
            return None

        debug(
            'msvc default:n_setup=%s, default_ismsvc=%s, msvc_tools=%s, tool_list=%s',
            cls.n_setup, cls.default_ismsvc, cls.msvc_tools, tool_list
        )

        if not cls.default_ismsvc:

            # Summary:
            #    * msvc is not installed
            #    * msvc version not specified (default)
            #    * msvc is not the default compiler

            # construct tools set
            tools_set = set(tool_list)

        else:

            if cls.n_setup == 1:
                # first setup and msvc is default compiler:
                #     build default tools regex for current tool state
                tools = cls.separator.join(tool_list)
                tools_nchar = len(tools)
                debug('msvc default:add regex:nchar=%d, tools=%s', tools_nchar, tools)
                re_default_tools = re.compile(re.escape(tools))
                cls.default_tools_re_list.insert(0, (tools_nchar, re_default_tools))
                # early exit: no error for default environment when msvc is not installed
                return None

            # Summary:
            #    * msvc is not installed
            #    * msvc version not specified (default)
            #    * environment tools list is not empty
            #    * default tools regex list constructed
            #    * msvc tools set constructed
            #
            # Algorithm using tools string and sets:
            #    * convert environment tools list to a string
            #    * iteratively remove default tools sequences via regex
            #      substition list built from longest sequence (first)
            #      to shortest sequence (last)
            #    * build environment tools set with remaining tools
            #    * compute intersection of environment tools and msvc tools sets
            #    * if the intersection is:
            #          empty - no error: default tools and/or no additional msvc tools
            #          not empty - error: user specified one or more msvc tool(s)
            #
            # This will not produce an error or warning when there are no
            # msvc installed instances nor any other recognized compilers
            # and the default environment is needed for a build.  The msvc
            # compiler is forcibly added to the environment tools list when
            # there are no compilers installed on win32. In this case, cl.exe
            # will not be found on the path resulting in a failed build.

            # construct tools string
            tools = cls.separator.join(tool_list)
            tools_nchar = len(tools)

            debug('msvc default:check tools:nchar=%d, tools=%s', tools_nchar, tools)

            # iteratively remove default tool sequences (longest to shortest)
            re_nchar_min, re_tools_min = cls.default_tools_re_list[-1]
            if tools_nchar >= re_nchar_min and re_tools_min.search(tools):
                # minimum characters satisfied and minimum pattern exists
                for re_nchar, re_default_tool in cls.default_tools_re_list:
                    if tools_nchar < re_nchar:
                        # not enough characters for pattern
                        continue
                    tools = re_default_tool.sub('', tools).strip(cls.separator)
                    tools_nchar = len(tools)
                    debug('msvc default:check tools:nchar=%d, tools=%s', tools_nchar, tools)
                    if tools_nchar < re_nchar_min or not re_tools_min.search(tools):
                        # less than minimum characters or minimum pattern does not exist
                        break

            # construct non-default list(s) tools set
            tools_set = {msvc_tool for msvc_tool in tools.split(cls.separator) if msvc_tool}

        debug('msvc default:tools=%s', tools_set)
        if not tools_set:
            return None

        # compute intersection of remaining tools set and msvc tools set
        tools_found = cls.msvc_tools.intersection(tools_set)
        debug('msvc default:tools_exist=%s', tools_found)
        if not tools_found:
            return None

        # construct in same order as tools list
        tools_found_list = []
        seen_tool = set()
        for tool in tool_list:
            if tool not in seen_tool:
                seen_tool.add(tool)
                if tool in tools_found:
                    tools_found_list.append(tool)

        # return tool list in order presented
        return tools_found_list

_Dispatcher.register(_MSVCSetupEnvDefault)

def get_default_version(env):
    msvc_version = env.get('MSVC_VERSION')
    msvs_version = env.get('MSVS_VERSION')
    debug('msvc_version:%s msvs_version:%s', msvc_version, msvs_version)

    if msvs_version and not msvc_version:
        SCons.Warnings.warn(
                SCons.Warnings.DeprecatedWarning,
                "MSVS_VERSION is deprecated: please use MSVC_VERSION instead ")
        return msvs_version
    elif msvc_version and msvs_version:
        if not msvc_version == msvs_version:
            SCons.Warnings.warn(
                    SCons.Warnings.VisualVersionMismatch,
                    "Requested msvc version (%s) and msvs version (%s) do " \
                    "not match: please use MSVC_VERSION only to request a " \
                    "visual studio version, MSVS_VERSION is deprecated" \
                    % (msvc_version, msvs_version))
        return msvs_version

    if not msvc_version:
        msvc_version = get_default_installed_msvc(env)
        if not msvc_version:
            #SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, msg)
            debug('No installed VCs')
            return None
        debug('using default installed MSVC version %s', repr(msvc_version))
    else:
        debug('using specified MSVC version %s', repr(msvc_version))

    return msvc_version

def msvc_setup_env_once(env, tool=None):
    try:
        has_run  = env["MSVC_SETUP_RUN"]
    except KeyError:
        has_run = False

    if not has_run:
        debug('tool=%s', repr(tool))
        _MSVCSetupEnvDefault.register_setup(env)
        msvc_setup_env(env)
        env["MSVC_SETUP_RUN"] = True

    req_tools = _MSVCSetupEnvDefault.register_iserror(env, tool)
    if req_tools:
        msg = "No versions of the MSVC compiler were found.\n" \
              "  Visual Studio C/C++ compilers may not be set correctly.\n" \
              "  Requested tool(s) are: {}".format(req_tools)
        _msvc_notfound_policy_handler(env, msg)

def msvc_find_valid_batch_script(env, version):
    """Find and execute appropriate batch script to set up build env.

    The MSVC build environment depends heavily on having the shell
    environment set.  SCons does not inherit that, and does not count
    on that being set up correctly anyway, so it tries to find the right
    MSVC batch script, or the right arguments to the generic batch script
    vcvarsall.bat, and run that, so we have a valid environment to build in.
    There are dragons here: the batch scripts don't fail (see comments
    elsewhere), they just leave you with a bad setup, so try hard to
    get it right.
    """

    # Find the host, target, and all candidate (host, target) platform combinations:
    platforms = get_host_target(env, version)
    debug("host_platform %s, target_platform %s host_target_list %s", *platforms)
    host_platform, target_platform, host_target_list = platforms

    d = None
    version_installed = False
    for host_arch, target_arch, in host_target_list:
        # Set to current arch.
        env['TARGET_ARCH'] = target_arch
        arg = ''

        # Try to locate a batch file for this host/target platform combo
        try:
            (vc_script, arg, vc_dir, sdk_script) = find_batch_file(env, version, host_arch, target_arch)
            debug('vc_script:%s vc_script_arg:%s sdk_script:%s', vc_script, arg, sdk_script)
            version_installed = True
        except VisualCException as e:
            msg = str(e)
            debug('Caught exception while looking for batch file (%s)', msg)
            version_installed = False
            continue

        # Try to use the located batch file for this host/target platform combo
        debug('use_script 2 %s, args:%s', repr(vc_script), arg)
        found = None
        if vc_script:
            arg = _ScriptArguments.msvc_script_arguments(env, version, vc_dir, arg)
            try:
                d = script_env(vc_script, args=arg)
                found = vc_script
            except BatchFileExecutionError as e:
                debug('use_script 3: failed running VC script %s: %s: Error:%s', repr(vc_script), arg, e)
                vc_script=None
                continue
        if not vc_script and sdk_script:
            debug('use_script 4: trying sdk script: %s', sdk_script)
            try:
                d = script_env(sdk_script)
                found = sdk_script
            except BatchFileExecutionError as e:
                debug('use_script 5: failed running SDK script %s: Error:%s', repr(sdk_script), e)
                continue
        elif not vc_script and not sdk_script:
            debug('use_script 6: Neither VC script nor SDK script found')
            continue

        debug("Found a working script/target: %s/%s", repr(found), arg)
        break # We've found a working target_platform, so stop looking

    # If we cannot find a viable installed compiler, reset the TARGET_ARCH
    # To it's initial value
    if not d:
        env['TARGET_ARCH'] = target_platform

        if version_installed:
            msg = "MSVC version '{}' working host/target script was not found.\n" \
                  "  Host = '{}', Target = '{}'\n" \
                  "  Visual Studio C/C++ compilers may not be set correctly".format(
                     version, host_platform, target_platform
                  )
        else:
            installed_vcs = get_installed_vcs(env)
            if installed_vcs:
                msg = "MSVC version '{}' was not found.\n" \
                      "  Visual Studio C/C++ compilers may not be set correctly.\n" \
                      "  Installed versions are: {}".format(version, installed_vcs)
            else:
                msg = "MSVC version '{}' was not found.\n" \
                      "  No versions of the MSVC compiler were found.\n" \
                      "  Visual Studio C/C++ compilers may not be set correctly".format(version)

        _msvc_notfound_policy_handler(env, msg)

    return d

_UNDEFINED = object()

def get_use_script_use_settings(env):

    #   use_script  use_settings   return values   action
    #     value       ignored      (value, None)   use script or bypass detection
    #   undefined  value not None  (False, value)  use dictionary
    #   undefined  undefined/None  (True,  None)   msvc detection

    # None (documentation) or evaluates False (code): bypass detection
    # need to distinguish between undefined and None
    use_script = env.get('MSVC_USE_SCRIPT', _UNDEFINED)

    if use_script != _UNDEFINED:
        # use_script defined, use_settings ignored (not type checked)
        return (use_script, None)

    # undefined or None: use_settings ignored
    use_settings = env.get('MSVC_USE_SETTINGS', None)

    if use_settings is not None:
        # use script undefined, use_settings defined and not None (type checked)
        return (False, use_settings)

    # use script undefined, use_settings undefined or None
    return (True, None)

def msvc_setup_env(env):
    debug('called')
    version = get_default_version(env)
    if version is None:
        if not msvc_setup_env_user(env):
            _MSVCSetupEnvDefault.set_nodefault()
        return None

    # XXX: we set-up both MSVS version for backward
    # compatibility with the msvs tool
    env['MSVC_VERSION'] = version
    env['MSVS_VERSION'] = version
    env['MSVS'] = {}

    use_script, use_settings = get_use_script_use_settings(env)
    if SCons.Util.is_String(use_script):
        use_script = use_script.strip()
        if not os.path.exists(use_script):
            raise MSVCScriptNotFound('Script specified by MSVC_USE_SCRIPT not found: "{}"'.format(use_script))
        args = env.subst('$MSVC_USE_SCRIPT_ARGS')
        debug('use_script 1 %s %s', repr(use_script), repr(args))
        d = script_env(use_script, args)
    elif use_script:
        d = msvc_find_valid_batch_script(env,version)
        debug('use_script 2 %s', d)
        if not d:
            return d
    elif use_settings is not None:
        if not SCons.Util.is_Dict(use_settings):
            error_msg = 'MSVC_USE_SETTINGS type error: expected a dictionary, found {}'.format(type(use_settings).__name__)
            raise MSVCUseSettingsError(error_msg)
        d = use_settings
        debug('use_settings %s', d)
    else:
        debug('MSVC_USE_SCRIPT set to False')
        warn_msg = "MSVC_USE_SCRIPT set to False, assuming environment " \
                   "set correctly."
        SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg)
        return None

    for k, v in d.items():
        env.PrependENVPath(k, v, delete_existing=True)
        debug("env['ENV']['%s'] = %s", k, env['ENV'][k])

    # final check to issue a warning if the compiler is not present
    if not find_program_path(env, 'cl'):
        debug("did not find %s", _CL_EXE_NAME)
        if CONFIG_CACHE:
            propose = "SCONS_CACHE_MSVC_CONFIG caching enabled, remove cache file {} if out of date.".format(CONFIG_CACHE)
        else:
            propose = "It may need to be installed separately with Visual Studio."
        warn_msg = "Could not find MSVC compiler 'cl'. {}".format(propose)
        SCons.Warnings.warn(SCons.Warnings.VisualCMissingWarning, warn_msg)

def msvc_exists(env=None, version=None):
    debug('version=%s', repr(version))
    vcs = get_installed_vcs(env)
    if version is None:
        rval = len(vcs) > 0
    else:
        rval = version in vcs
    debug('version=%s, return=%s', repr(version), rval)
    return rval

def msvc_setup_env_user(env=None):
    rval = False
    if env:

        # Intent is to use msvc tools:
        #     MSVC_VERSION or MSVS_VERSION: defined and is True
        #     MSVC_USE_SCRIPT:   defined and (is string or is False)
        #     MSVC_USE_SETTINGS: defined and is not None

        # defined and is True
        for key in ['MSVC_VERSION', 'MSVS_VERSION']:
            if key in env and env[key]:
                rval = True
                debug('key=%s, return=%s', repr(key), rval)
                return rval

        # defined and (is string or is False)
        for key in ['MSVC_USE_SCRIPT']:
            if key in env and (SCons.Util.is_String(env[key]) or not env[key]):
                rval = True
                debug('key=%s, return=%s', repr(key), rval)
                return rval

        # defined and is not None
        for key in ['MSVC_USE_SETTINGS']:
            if key in env and env[key] is not None:
                rval = True
                debug('key=%s, return=%s', repr(key), rval)
                return rval

    debug('return=%s', rval)
    return rval

def msvc_setup_env_tool(env=None, version=None, tool=None):
    debug('tool=%s, version=%s', repr(tool), repr(version))
    _MSVCSetupEnvDefault.register_tool(env, tool)
    rval = False
    if not rval and msvc_exists(env, version):
        rval = True
    if not rval and msvc_setup_env_user(env):
        rval = True
    debug('tool=%s, version=%s, return=%s', repr(tool), repr(version), rval)
    return rval

def get_msvc_sdk_versions(msvc_version=None, msvc_uwp_app=False):
    debug('msvc_version=%s, msvc_uwp_app=%s', repr(msvc_version), repr(msvc_uwp_app))

    rval = []

    if not msvc_version:
        msvc_version = get_default_installed_msvc()

    if not msvc_version:
        debug('no msvc versions detected')
        return rval

    rval = _WindowsSDK.get_msvc_sdk_version_list(msvc_version, msvc_uwp_app)
    return rval

class _Util:

    @staticmethod
    def listdir_dirs(p):
        dirs = []
        for dir_name in os.listdir(p):
            dir_path = os.path.join(p, dir_name)
            if os.path.isdir(dir_path):
                dirs.append((dir_name, dir_path))
        return dirs

    @staticmethod
    def process_path(p):
        if p:
            p = os.path.normpath(p)
            p = os.path.realpath(p)
            p = os.path.normcase(p)
        return p

class _Registry:

    def read_value(hkey, subkey_valname):
        try:
            rval = common.read_reg(subkey_valname, hkroot=hkey)
        except OSError:
            debug('OSError: hkey=%s, subkey=%s', repr(hkey), repr(subkey_valname))
            return None
        except IndexError:
            debug('IndexError: hkey=%s, subkey=%s', repr(hkey), repr(subkey_valname))
            return None
        debug('hkey=%s, subkey=%s, rval=%s', repr(hkey), repr(subkey_valname), repr(rval))
        return rval

    @classmethod
    def registry_query_path(cls, key, val, suffix):
        extval = val + '\\' + suffix if suffix else val
        qpath = cls.read_value(key, extval)
        if qpath and os.path.exists(qpath):
            qpath = _Util.process_path(qpath)
        else:
            qpath = None
        return (qpath, key, val, extval)

    REG_SOFTWARE_MICROSOFT = [
        (SCons.Util.HKEY_LOCAL_MACHINE, r'Software\Wow6432Node\Microsoft'),
        (SCons.Util.HKEY_CURRENT_USER,  r'Software\Wow6432Node\Microsoft'), # SDK queries
        (SCons.Util.HKEY_LOCAL_MACHINE, r'Software\Microsoft'),
        (SCons.Util.HKEY_CURRENT_USER,  r'Software\Microsoft'),
    ]

    @classmethod
    def microsoft_query_paths(cls, suffix, usrval=None):
        paths = []
        records = []
        for key, val in cls.REG_SOFTWARE_MICROSOFT:
            extval = val + '\\' + suffix if suffix else val
            qpath = cls.read_value(key, extval)
            if qpath and os.path.exists(qpath):
                qpath = _Util.process_path(qpath)
                if qpath not in paths:
                    paths.append(qpath)
                    records.append((qpath, key, val, extval, usrval))
        return records

    @classmethod
    def microsoft_query_keys(cls, suffix, usrval=None):
        records = []
        for key, val in cls.REG_SOFTWARE_MICROSOFT:
            extval = val + '\\' + suffix if suffix else val
            rval = cls.read_value(key, extval)
            if rval:
                records.append((key, val, extval, usrval))
        return records

    @classmethod
    def microsoft_sdks(cls, version):
        return '\\'.join([r'Microsoft SDKs\Windows', 'v' + version, r'InstallationFolder'])

    @classmethod
    def sdk_query_paths(cls, version):
        q = cls.microsoft_sdks(version)
        return cls.microsoft_query_paths(q)

    @classmethod
    def windows_kits(cls, version):
        return r'Windows Kits\Installed Roots\KitsRoot' + version

    @classmethod
    def windows_kit_query_paths(cls, version):
        q = cls.windows_kits(version)
        return cls.microsoft_query_paths(q)

class _WindowsSDK:

    @classmethod
    def _new_sdk_map(cls):
        sdk_map = {
            'desktop': [],
            'uwp': [],
        }
        return sdk_map

    @classmethod
    def _sdk_10_layout(cls, version):

        folder_prefix = version + '.'

        sdk_map = cls._new_sdk_map()

        sdk_roots = _Registry.sdk_query_paths(version)

        sdk_version_platform_seen = set()
        sdk_roots_seen = set()

        for sdk_t in sdk_roots:

            sdk_root = sdk_t[0]
            if sdk_root in sdk_roots_seen:
                continue
            sdk_roots_seen.add(sdk_root)

            if not os.path.exists(sdk_root):
                continue

            sdk_include_path = os.path.join(sdk_root, 'include')
            if not os.path.exists(sdk_include_path):
                continue

            for version_nbr, version_nbr_path in _Util.listdir_dirs(sdk_include_path):

                if not version_nbr.startswith(folder_prefix):
                    continue

                sdk_inc_path = _Util.process_path(os.path.join(version_nbr_path, 'um'))
                if not os.path.exists(sdk_inc_path):
                    continue

                for platform_type, sdk_inc_file in [
                    ('desktop', 'winsdkver.h'),
                    ('uwp',     'windows.h'),
                ]:

                    if not os.path.exists(os.path.join(sdk_inc_path, sdk_inc_file)):
                        continue

                    key = (version_nbr, platform_type)
                    if key in sdk_version_platform_seen:
                        continue
                    sdk_version_platform_seen.add(key)

                    sdk_map[platform_type].append(version_nbr)

        for key, val in sdk_map.items():
            val.sort(reverse=True)

        return sdk_map

    @classmethod
    def _sdk_81_layout(cls, version):

        version_nbr = version

        sdk_map = cls._new_sdk_map()

        sdk_roots = _Registry.sdk_query_paths(version)

        sdk_version_platform_seen = set()
        sdk_roots_seen = set()

        for sdk_t in sdk_roots:

            sdk_root = sdk_t[0]
            if sdk_root in sdk_roots_seen:
                continue
            sdk_roots_seen.add(sdk_root)

            # msvc does not check for existence of root or other files

            sdk_inc_path = _Util.process_path(os.path.join(sdk_root, r'include\um'))
            if not os.path.exists(sdk_inc_path):
                continue

            for platform_type, sdk_inc_file in [
                ('desktop', 'winsdkver.h'),
                ('uwp',     'windows.h'),
            ]:

                if not os.path.exists(os.path.join(sdk_inc_path, sdk_inc_file)):
                    continue

                key = (version_nbr, platform_type)
                if key in sdk_version_platform_seen:
                    continue
                sdk_version_platform_seen.add(key)

                sdk_map[platform_type].append(version_nbr)

        for key, val in sdk_map.items():
            val.sort(reverse=True)

        return sdk_map

    sdk_map_cache = {}
    sdk_cache = {}

    @classmethod
    def _reset_sdk_cache(cls):
        debug('reset %s: sdk cache', cls.__name__)
        cls._sdk_map_cache = {}
        cls._sdk_cache = {}

    @classmethod
    def _sdk_10(cls, key, reg_version):
        if key in cls.sdk_map_cache:
            sdk_map = cls.sdk_map_cache[key]
        else:
            sdk_map = cls._sdk_10_layout(reg_version)
            cls.sdk_map_cache[key] = sdk_map
        return sdk_map

    @classmethod
    def _sdk_81(cls, key, reg_version):
        if key in cls.sdk_map_cache:
            sdk_map = cls.sdk_map_cache[key]
        else:
            sdk_map = cls._sdk_81_layout(reg_version)
            cls.sdk_map_cache[key] = sdk_map
        return sdk_map

    @classmethod
    def _combine_sdk_map_list(cls, sdk_map_list):
        combined_sdk_map = cls._new_sdk_map()
        for sdk_map in sdk_map_list:
            for key, val in sdk_map.items():
                combined_sdk_map[key].extend(val)
        return combined_sdk_map

    sdk_dispatch_map = None

    @classmethod
    def _init_sdk_dispatch_map(cls):
        cls.sdk_dispatch_map = {
            '10.0': (cls._sdk_10, '10.0'),
            '8.1': (cls._sdk_81, '8.1'),
        }

    @classmethod
    def _verify_sdk_dispatch_map(cls):
        debug('%s verify sdk_dispatch_map', cls.__name__)
        cls._init_sdk_dispatch_map()
        for sdk_version in _Config.MSVC_SDK_VERSIONS:
            if sdk_version in cls.sdk_dispatch_map:
                continue
            err_msg = 'sdk version {} not in {}.sdk_dispatch_map'.format(sdk_version, cls.__name__)
            raise MSVCInternalError(err_msg)
        return None

    @classmethod
    def _version_list_sdk_map(cls, version_list):

        if not cls.sdk_dispatch_map:
            cls._init_sdk_dispatch_map()

        sdk_map_list = []
        for version in version_list:
            func, reg_version = cls.sdk_dispatch_map[version]
            sdk_map = func(version, reg_version)
            sdk_map_list.append(sdk_map)

        combined_sdk_map = cls._combine_sdk_map_list(sdk_map_list)
        return combined_sdk_map

    @classmethod
    def _sdk_map(cls, version_list):
        key = tuple(version_list)
        if key in cls.sdk_cache:
            sdk_map = cls.sdk_cache[key]
        else:
            version_numlist = [float(v) for v in version_list]
            version_numlist.sort(reverse=True)
            key = tuple([str(v) for v in version_numlist])
            sdk_map = cls._version_list_sdk_map(key)
            cls.sdk_cache[key] = sdk_map
        return sdk_map

    @classmethod
    def get_sdk_version_list(cls, version_list, platform_type):
        sdk_map = cls._sdk_map(version_list)
        sdk_list = sdk_map.get(platform_type, [])
        return sdk_list

    @classmethod
    def get_msvc_sdk_version_list(cls, msvc_version=None, msvc_uwp_app=False):
        debug('msvc_version=%s, msvc_uwp_app=%s', repr(msvc_version), repr(msvc_uwp_app))

        sdk_versions = []

        verstr = get_msvc_version_numeric(msvc_version)
        vs_def = _Config.MSVC_VERSION_EXTERNAL.get(verstr, None)
        if not vs_def:
            debug('vs_def is not defined')
            return sdk_versions

        is_uwp = True if msvc_uwp_app in _Config.BOOLEAN_SYMBOLS[True] else False
        platform_type = 'uwp' if is_uwp else 'desktop'
        sdk_list = _WindowsSDK.get_sdk_version_list(vs_def.vc_sdk_versions, platform_type)

        sdk_versions.extend(sdk_list)
        debug('sdk_versions=%s', repr(sdk_versions))

        return sdk_versions

    @classmethod
    def reset(cls):
        debug('reset %s', cls.__name__)
        cls._reset_sdk_cache()

    @classmethod
    def verify(cls):
        debug('verify %s', cls.__name__)
        cls._verify_sdk_dispatch_map()

_Dispatcher.register(_WindowsSDK)

class _ScriptArguments:

    # TODO: verify SDK 10 version folder names 10.0.XXXXX.0 {1,3} last?
    re_sdk_version_100 = re.compile(r'^10[.][0-9][.][0-9]{5}[.][0-9]{1}$')
    re_sdk_version_81 = re.compile(r'^8[.]1$')

    re_sdk_dispatch_map = {
        '10.0': re_sdk_version_100,
        '8.1': re_sdk_version_81,
    }

    @classmethod
    def _verify_re_sdk_dispatch_map(cls):
        debug('%s verify re_sdk_dispatch_map', cls.__name__)
        for sdk_version in _Config.MSVC_SDK_VERSIONS:
            if sdk_version in cls.re_sdk_dispatch_map:
                continue
            err_msg = 'sdk version {} not in {}.re_sdk_dispatch_map'.format(sdk_version, cls.__name__)
            raise MSVCInternalError(err_msg)
        return None

    # capture msvc version
    re_toolset_version = re.compile(r'^(?P<version>[1-9][0-9]?[.][0-9])[0-9.]*$', re.IGNORECASE)

    re_toolset_full = re.compile(r'''^(?:
        (?:[1-9][0-9][.][0-9]{1,2})|           # XX.Y    - XX.YY
        (?:[1-9][0-9][.][0-9]{2}[.][0-9]{1,5}) # XX.YY.Z - XX.YY.ZZZZZ
    )$''', re.VERBOSE)

    re_toolset_140 = re.compile(r'''^(?:
        (?:14[.]0{1,2})|       # 14.0    - 14.00
        (?:14[.]0{2}[.]0{1,5}) # 14.00.0 - 14.00.00000
    )$''', re.VERBOSE)

    # valid SxS formats will be matched with re_toolset_full: match 3 '.' format
    re_toolset_sxs = re.compile(r'^[1-9][0-9][.][0-9]{2}[.][0-9]{2}[.][0-9]{1,2}$')

    # MSVC_SCRIPT_ARGS
    re_vcvars_uwp = re.compile(r'(?:(?<!\S)|^)(?P<uwp>(?:uwp|store))(?:(?!\S)|$)',re.IGNORECASE)
    re_vcvars_sdk = re.compile(r'(?:(?<!\S)|^)(?P<sdk>(?:[1-9][0-9]*[.]\S*))(?:(?!\S)|$)',re.IGNORECASE)
    re_vcvars_toolset = re.compile(r'(?:(?<!\S)|^)(?P<toolset_arg>(?:[-]{1,2}|[/])vcvars_ver[=](?P<toolset>\S*))(?:(?!\S)|$)', re.IGNORECASE)
    re_vcvars_spectre = re.compile(r'(?:(?<!\S)|^)(?P<spectre_arg>(?:[-]{1,2}|[/])vcvars_spectre_libs[=](?P<spectre>\S*))(?:(?!\S)|$)',re.IGNORECASE)

    # Force default sdk argument
    MSVC_FORCE_DEFAULT_SDK = False

    # Force default toolset argument
    MSVC_FORCE_DEFAULT_TOOLSET = False

    # MSVC batch file arguments:
    #
    #     VS2022: UWP, SDK, TOOLSET, SPECTRE
    #     VS2019: UWP, SDK, TOOLSET, SPECTRE
    #     VS2017: UWP, SDK, TOOLSET, SPECTRE
    #     VS2015: UWP, SDK
    #
    #     MSVC_SCRIPT_ARGS:     VS2015+
    #
    #     MSVC_UWP_APP:         VS2015+
    #     MSVC_SDK_VERSION:     VS2015+
    #     MSVC_TOOLSET_VERSION: VS2017+
    #     MSVC_SPECTRE_LIBS:    VS2017+

    @enum.unique
    class SortOrder(enum.IntEnum):
        ARCH = 0     # arch
        UWP = 1      # MSVC_UWP_APP
        SDK = 2      # MSVC_SDK_VERSION
        TOOLSET = 3  # MSVC_TOOLSET_VERSION
        SPECTRE = 4  # MSVC_SPECTRE_LIBS
        USER = 5     # MSVC_SCRIPT_ARGS

    VS2019 = _Config.MSVS_VERSION_INTERNAL['2019']
    VS2017 = _Config.MSVS_VERSION_INTERNAL['2017']
    VS2015 = _Config.MSVS_VERSION_INTERNAL['2015']

    MSVC_VERSION_ARGS_DEFINITION = namedtuple('MSVCVersionArgsDefinition', [
        'version', # fully qualified msvc version (e.g., '14.1Exp')
        'vs_def',
    ])

    @classmethod
    def _msvc_version(cls, version):

        verstr = get_msvc_version_numeric(version)
        vs_def = _Config.MSVC_VERSION_INTERNAL[verstr]

        version_args = cls.MSVC_VERSION_ARGS_DEFINITION(
            version = version,
            vs_def = vs_def,
        )

        return version_args

    @classmethod
    def _msvc_script_argument_uwp(cls, env, msvc, arglist):

        uwp_app = env['MSVC_UWP_APP']
        debug('MSVC_VERSION=%s, MSVC_UWP_APP=%s', repr(msvc.version), repr(uwp_app))

        if not uwp_app:
            return None

        if uwp_app not in _Config.BOOLEAN_SYMBOLS[True]:
            return None

        if msvc.vs_def.vc_buildtools_def.vc_version_numeric < cls.VS2015.vc_buildtools_def.vc_version_numeric:
            debug(
                'invalid: msvc version constraint: %s < %s VS2015',
                repr(msvc.vs_def.vc_buildtools_def.vc_version_numeric),
                repr(cls.VS2015.vc_buildtools_def.vc_version_numeric)
            )
            err_msg = "MSVC_UWP_APP ({}) constraint violation: MSVC_VERSION {} < {} VS2015".format(
                repr(uwp_app), repr(msvc.version), repr(cls.VS2015.vc_buildtools_def.vc_version)
            )
            raise MSVCArgumentError(err_msg)

        # VS2017+ rewrites uwp => store for 14.0 toolset
        uwp_arg = msvc.vs_def.vc_uwp

        # store/uwp may not be fully installed
        argpair = (cls.SortOrder.UWP, uwp_arg)
        arglist.append(argpair)

        return uwp_arg

    @classmethod
    def _user_script_argument_uwp(cls, env, uwp, user_argstr):

        matches = [m for m in cls.re_vcvars_uwp.finditer(user_argstr)]
        if not matches:
            return None

        if len(matches) > 1:
            debug('multiple uwp declarations: MSVC_SCRIPT_ARGS=%s', repr(user_argstr))
            err_msg = "multiple uwp declarations: MSVC_SCRIPT_ARGS={}".format(repr(user_argstr))
            raise MSVCArgumentError(err_msg)

        if not uwp:
            return None

        env_argstr = env.get('MSVC_UWP_APP','')
        debug('multiple uwp declarations: MSVC_UWP_APP=%s, MSVC_SCRIPT_ARGS=%s', repr(env_argstr), repr(user_argstr))

        err_msg = "multiple uwp declarations: MSVC_UWP_APP={} and MSVC_SCRIPT_ARGS={}".format(
            repr(env_argstr), repr(user_argstr)
        )

        raise MSVCArgumentError(err_msg)

    @classmethod
    def _msvc_script_argument_sdk_constraints(cls, msvc, sdk_version):

        if msvc.vs_def.vc_buildtools_def.vc_version_numeric < cls.VS2015.vc_buildtools_def.vc_version_numeric:
            debug(
                'invalid: msvc_version constraint: %s < %s VS2015',
                repr(msvc.vs_def.vc_buildtools_def.vc_version_numeric),
                repr(cls.VS2015.vc_buildtools_def.vc_version_numeric)
            )
            err_msg = "MSVC_SDK_VERSION ({}) constraint violation: MSVC_VERSION {} < {} VS2015".format(
                repr(sdk_version), repr(msvc.version), repr(cls.VS2015.vc_buildtools_def.vc_version)
            )
            return err_msg

        for msvc_sdk_version in msvc.vs_def.vc_sdk_versions:
            re_sdk_version = cls.re_sdk_dispatch_map[msvc_sdk_version]
            if re_sdk_version.match(sdk_version):
                debug('valid: sdk_version=%s', repr(sdk_version))
                return None

        debug('invalid: method exit: sdk_version=%s', repr(sdk_version))
        err_msg = "MSVC_SDK_VERSION ({}) is not supported".format(repr(sdk_version))
        return err_msg

    @classmethod
    def _msvc_script_argument_sdk(cls, env, msvc, platform_type, arglist):

        sdk_version = env['MSVC_SDK_VERSION']
        debug(
            'MSVC_VERSION=%s, MSVC_SDK_VERSION=%s, platform_type=%s',
            repr(msvc.version), repr(sdk_version), repr(platform_type)
        )

        if not sdk_version:
            return None

        err_msg = cls._msvc_script_argument_sdk_constraints(msvc, sdk_version)
        if err_msg:
            raise MSVCArgumentError(err_msg)

        sdk_list = _WindowsSDK.get_sdk_version_list(msvc.vs_def.vc_sdk_versions, platform_type)

        if sdk_version not in sdk_list:
            err_msg = "MSVC_SDK_VERSION {} not found for platform type {}".format(
                repr(sdk_version), repr(platform_type)
            )
            raise MSVCArgumentError(err_msg)

        argpair = (cls.SortOrder.SDK, sdk_version)
        arglist.append(argpair)

        return sdk_version

    @classmethod
    def _msvc_script_default_sdk(cls, env, msvc, platform_type, arglist):

        if msvc.vs_def.vc_buildtools_def.vc_version_numeric < cls.VS2015.vc_buildtools_def.vc_version_numeric:
            return None

        sdk_list = _WindowsSDK.get_sdk_version_list(msvc.vs_def.vc_sdk_versions, platform_type)
        if not len(sdk_list):
            return None

        sdk_default = sdk_list[0]

        debug(
            'MSVC_VERSION=%s, sdk_default=%s, platform_type=%s',
            repr(msvc.version), repr(sdk_default), repr(platform_type)
        )

        argpair = (cls.SortOrder.SDK, sdk_default)
        arglist.append(argpair)

        return sdk_default

    @classmethod
    def _user_script_argument_sdk(cls, env, sdk_version, user_argstr):

        matches = [m for m in cls.re_vcvars_sdk.finditer(user_argstr)]
        if not matches:
            return None

        if len(matches) > 1:
            debug('multiple sdk version declarations: MSVC_SCRIPT_ARGS=%s', repr(user_argstr))
            err_msg = "multiple sdk version declarations: MSVC_SCRIPT_ARGS={}".format(repr(user_argstr))
            raise MSVCArgumentError(err_msg)

        if not sdk_version:
            user_sdk = matches[0].group('sdk')
            return user_sdk

        env_argstr = env.get('MSVC_SDK_VERSION','')
        debug('multiple sdk version declarations: MSVC_SDK_VERSION=%s, MSVC_SCRIPT_ARGS=%s', repr(env_argstr), repr(user_argstr))

        err_msg = "multiple sdk version declarations: MSVC_SDK_VERSION={} and MSVC_SCRIPT_ARGS={}".format(
            repr(env_argstr), repr(user_argstr)
        )

        raise MSVCArgumentError(err_msg)

    @classmethod
    def _msvc_read_toolset_file(cls, msvc, filename):
        toolset_version = None
        try:
            with open(filename) as f:
                toolset_version = f.readlines()[0].strip()
            debug(
                'msvc_version=%s, filename=%s, toolset_version=%s',
                repr(msvc.version), repr(filename), repr(toolset_version)
            )
        except OSError:
            debug('OSError: msvc_version=%s, filename=%s', repr(msvc.version), repr(filename))
        except IndexError:
            debug('IndexError: msvc_version=%s, filename=%s', repr(msvc.version), repr(filename))
        return toolset_version

    @classmethod
    def _msvc_read_toolset_folders(cls, msvc, vc_dir):

        toolsets_sxs = {}
        toolsets_full = []

        build_dir = os.path.join(vc_dir, "Auxiliary", "Build")
        sxs_toolsets = [f.name for f in os.scandir(build_dir) if f.is_dir()]
        for sxs_toolset in sxs_toolsets:
            filename = 'Microsoft.VCToolsVersion.{}.txt'.format(sxs_toolset)
            filepath = os.path.join(build_dir, sxs_toolset, filename)
            debug('sxs toolset: check file=%s', repr(filepath))
            if os.path.exists(filepath):
                toolset_version = cls._msvc_read_toolset_file(msvc, filepath)
                if not toolset_version:
                    continue
                toolsets_sxs[sxs_toolset] = toolset_version
                debug(
                    'sxs toolset: msvc_version=%s, sxs_version=%s, toolset_version=%s',
                    repr(msvc.version), repr(sxs_toolset), toolset_version
                )

        toolset_dir = os.path.join(vc_dir, "Tools", "MSVC")
        toolsets = [f.name for f in os.scandir(toolset_dir) if f.is_dir()]
        for toolset_version in toolsets:
            binpath = os.path.join(toolset_dir, toolset_version, "bin")
            debug('toolset: check binpath=%s', repr(binpath))
            if os.path.exists(binpath):
                toolsets_full.append(toolset_version)
                debug(
                    'toolset: msvc_version=%s, toolset_version=%s',
                    repr(msvc.version), repr(toolset_version)
                )

        toolsets_full.sort(reverse=True)
        debug('msvc_version=%s, toolsets=%s', repr(msvc.version), repr(toolsets_full))

        return toolsets_sxs, toolsets_full

    @classmethod
    def _msvc_read_toolset_default(cls, msvc, vc_dir):

        build_dir = os.path.join(vc_dir, "Auxiliary", "Build")

        # VS2019+
        filename = "Microsoft.VCToolsVersion.{}.default.txt".format(msvc.vs_def.vc_buildtools_def.vc_buildtools)
        filepath = os.path.join(build_dir, filename)

        debug('default toolset: check file=%s', repr(filepath))
        if os.path.exists(filepath):
            toolset_buildtools = cls._msvc_read_toolset_file(msvc, filepath)
            if toolset_buildtools:
                return toolset_buildtools

        # VS2017+
        filename = "Microsoft.VCToolsVersion.default.txt"
        filepath = os.path.join(build_dir, filename)

        debug('default toolset: check file=%s', repr(filepath))
        if os.path.exists(filepath):
            toolset_default = cls._msvc_read_toolset_file(msvc, filepath)
            if toolset_default:
                return toolset_default

        return None

    _toolset_version_cache = {}
    _toolset_default_cache = {}

    @classmethod
    def _reset_toolset_cache(cls):
        debug('reset %s: toolset cache', cls.__name__)
        cls._toolset_version_cache = {}
        cls._toolset_default_cache = {}

    @classmethod
    def _msvc_version_toolsets(cls, msvc, vc_dir):

        if msvc.version in cls._toolset_version_cache:
            toolsets_sxs, toolsets_full = cls._toolset_version_cache[msvc.version]
        else:
            toolsets_sxs, toolsets_full = cls._msvc_read_toolset_folders(msvc, vc_dir)
            cls._toolset_version_cache[msvc.version] = toolsets_sxs, toolsets_full

        return toolsets_sxs, toolsets_full

    @classmethod
    def _msvc_default_toolset(cls, msvc, vc_dir):

        if msvc.version in cls._toolset_default_cache:
            toolset_default = cls._toolset_default_cache[msvc.version]
        else:
            toolset_default = cls._msvc_read_toolset_default(msvc, vc_dir)
            cls._toolset_default_cache[msvc.version] = toolset_default

        return toolset_default

    @classmethod
    def _msvc_version_toolset_vcvars(cls, msvc, vc_dir, toolset_version):

        if toolset_version == '14.0':
            return toolset_version

        toolsets_sxs, toolsets_full = cls._msvc_version_toolsets(msvc, vc_dir)

        if msvc.vs_def.vc_buildtools_def.vc_version_numeric == cls.VS2019.vc_buildtools_def.vc_version_numeric:
            # necessary to detect toolset not found
            if toolset_version == '14.28.16.8':
                new_toolset_version = '14.28'
                # VS2019\Common7\Tools\vsdevcmd\ext\vcvars.bat AzDO Bug#1293526
                #     special handling of the 16.8 SxS toolset, use VC\Auxiliary\Build\14.28 directory and SxS files
                #     if SxS version 14.28 not present/installed, fallback selection of toolset VC\Tools\MSVC\14.28.nnnnn.
                debug(
                    'rewrite toolset_version=%s => toolset_version=%s',
                    repr(toolset_version), repr(new_toolset_version)
                )
                toolset_version = new_toolset_version

        if toolset_version in toolsets_sxs:
            toolset_vcvars = toolsets_sxs[toolset_version]
            return toolset_vcvars

        for toolset_full in toolsets_full:
            if toolset_full.startswith(toolset_version):
                toolset_vcvars = toolset_full
                return toolset_vcvars

        return None

    @classmethod
    def _msvc_script_argument_toolset_constraints(cls, msvc, toolset_version):

        if msvc.vs_def.vc_buildtools_def.vc_version_numeric < cls.VS2017.vc_buildtools_def.vc_version_numeric:
            debug(
                'invalid: msvc version constraint: %s < %s VS2017',
                repr(msvc.vs_def.vc_buildtools_def.vc_version_numeric),
                repr(cls.VS2017.vc_buildtools_def.vc_version_numeric)
            )
            err_msg = "MSVC_TOOLSET_VERSION ({}) constraint violation: MSVC_VERSION {} < {} VS2017".format(
                repr(toolset_version), repr(msvc.version), repr(cls.VS2017.vc_buildtools_def.vc_version)
            )
            return err_msg

        m = cls.re_toolset_version.match(toolset_version)
        if not m:
            debug('invalid: re_toolset_version: toolset_version=%s', repr(toolset_version))
            err_msg = 'MSVC_TOOLSET_VERSION {} format is not supported'.format(
                repr(toolset_version)
            )
            return err_msg

        toolset_ver = m.group('version')
        toolset_vernum = float(toolset_ver)

        if toolset_vernum < cls.VS2015.vc_buildtools_def.vc_version_numeric:
            debug(
                'invalid: toolset version constraint: %s < %s VS2015',
                repr(toolset_vernum), repr(cls.VS2015.vc_buildtools_def.vc_version_numeric)
            )
            err_msg = "MSVC_TOOLSET_VERSION ({}) constraint violation: toolset version {} < {} VS2015".format(
                repr(toolset_version), repr(toolset_ver), repr(cls.VS2015.vc_buildtools_def.vc_version)
            )
            return err_msg

        if toolset_vernum > msvc.vs_def.vc_buildtools_def.vc_version_numeric:
            debug(
                'invalid: toolset version constraint: toolset %s > %s msvc',
                repr(toolset_vernum), repr(msvc.vs_def.vc_buildtools_def.vc_version_numeric)
            )
            err_msg = "MSVC_TOOLSET_VERSION ({}) constraint violation: toolset version {} > {} MSVC_VERSION".format(
                repr(toolset_version), repr(toolset_ver), repr(msvc.version)
            )
            return err_msg

        if toolset_vernum == cls.VS2015.vc_buildtools_def.vc_version_numeric:
            # tooset = 14.0
            if cls.re_toolset_full.match(toolset_version):
                if not cls.re_toolset_140.match(toolset_version):
                    debug(
                        'invalid: toolset version 14.0 constraint: %s != 14.0',
                        repr(toolset_version)
                    )
                    err_msg = "MSVC_TOOLSET_VERSION ({}) constraint violation: toolset version {} != '14.0'".format(
                        repr(toolset_version), repr(toolset_version)
                    )
                    return err_msg
                return None

        if cls.re_toolset_full.match(toolset_version):
            debug('valid: re_toolset_full: toolset_version=%s', repr(toolset_version))
            return None

        if cls.re_toolset_sxs.match(toolset_version):
            debug('valid: re_toolset_sxs: toolset_version=%s', repr(toolset_version))
            return None

        debug('invalid: method exit: toolset_version=%s', repr(toolset_version))
        err_msg = "MSVC_TOOLSET_VERSION ({}) format is not supported".format(repr(toolset_version))
        return err_msg

    @classmethod
    def _msvc_script_argument_toolset(cls, env, msvc, vc_dir, arglist):

        toolset_version = env['MSVC_TOOLSET_VERSION']
        debug('MSVC_VERSION=%s, MSVC_TOOLSET_VERSION=%s', repr(msvc.version), repr(toolset_version))

        if not toolset_version:
            return None

        err_msg = cls._msvc_script_argument_toolset_constraints(msvc, toolset_version)
        if err_msg:
            raise MSVCArgumentError(err_msg)

        if toolset_version.startswith('14.0') and len(toolset_version) > len('14.0'):
            new_toolset_version = '14.0'
            debug(
                'rewrite toolset_version=%s => toolset_version=%s',
                repr(toolset_version), repr(new_toolset_version)
            )
            toolset_version = new_toolset_version

        toolset_vcvars = cls._msvc_version_toolset_vcvars(msvc, vc_dir, toolset_version)
        debug(
            'toolset: toolset_version=%s, toolset_vcvars=%s',
            repr(toolset_version), repr(toolset_vcvars)
        )

        if not toolset_vcvars:
            err_msg = "MSVC_TOOLSET_VERSION {} not found for MSVC_VERSION {}".format(
                repr(toolset_version), repr(msvc.version)
            )
            raise MSVCArgumentError(err_msg)

        argpair = (cls.SortOrder.TOOLSET, '-vcvars_ver={}'.format(toolset_vcvars))
        arglist.append(argpair)

        return toolset_vcvars

    @classmethod
    def _msvc_script_default_toolset(cls, env, msvc, vc_dir, arglist):

        if msvc.vs_def.vc_buildtools_def.vc_version_numeric < cls.VS2017.vc_buildtools_def.vc_version_numeric:
            return None

        toolset_default = cls._msvc_default_toolset(msvc, vc_dir)
        if not toolset_default:
            return None

        debug('MSVC_VERSION=%s, toolset_default=%s', repr(msvc.version), repr(toolset_default))

        argpair = (cls.SortOrder.TOOLSET, '-vcvars_ver={}'.format(toolset_default))
        arglist.append(argpair)

        return toolset_default

    @classmethod
    def _user_script_argument_toolset(cls, env, toolset_version, user_argstr):

        matches = [m for m in cls.re_vcvars_toolset.finditer(user_argstr)]
        if not matches:
            return None

        if len(matches) > 1:
            debug('multiple toolset version declarations: MSVC_SCRIPT_ARGS=%s', repr(user_argstr))
            err_msg = "multiple toolset version declarations: MSVC_SCRIPT_ARGS={}".format(repr(user_argstr))
            raise MSVCArgumentError(err_msg)

        if not toolset_version:
            user_toolset = matches[0].group('toolset')
            return user_toolset

        env_argstr = env.get('MSVC_TOOLSET_VERSION','')
        debug('multiple toolset version declarations: MSVC_TOOLSET_VERSION=%s, MSVC_SCRIPT_ARGS=%s', repr(env_argstr), repr(user_argstr))

        err_msg = "multiple toolset version declarations: MSVC_TOOLSET_VERSION={} and MSVC_SCRIPT_ARGS={}".format(
            repr(env_argstr), repr(user_argstr)
        )

        raise MSVCArgumentError(err_msg)

    @classmethod
    def _msvc_script_argument_spectre(cls, env, msvc, arglist):

        spectre_libs = env['MSVC_SPECTRE_LIBS']
        debug('MSVC_VERSION=%s, MSVC_SPECTRE_LIBS=%s', repr(msvc.version), repr(spectre_libs))

        if not spectre_libs:
            return None

        if spectre_libs not in (True, '1'):
            return None

        if msvc.vs_def.vc_buildtools_def.vc_version_numeric < cls.VS2017.vc_buildtools_def.vc_version_numeric:
            debug(
                'invalid: msvc version constraint: %s < %s VS2017',
                repr(msvc.vs_def.vc_buildtools_def.vc_version_numeric),
                repr(cls.VS2017.vc_buildtools_def.vc_version_numeric)
            )
            err_msg = "MSVC_SPECTRE_LIBS ({}) constraint violation: MSVC_VERSION {} < {} VS2017".format(
                repr(spectre_libs), repr(msvc.version), repr(cls.VS2017.vc_buildtools_def.vc_version)
            )
            raise MSVCArgumentError(err_msg)

        spectre_arg = 'spectre'

        # spectre libs may not be installed
        argpair = (cls.SortOrder.SPECTRE, '-vcvars_spectre_libs={}'.format(spectre_arg))
        arglist.append(argpair)

        return spectre_arg

    @classmethod
    def _msvc_script_argument_user(cls, env, msvc, arglist):

        # subst None -> empty string
        script_args = env.subst('$MSVC_SCRIPT_ARGS')
        debug('MSVC_VERSION=%s, MSVC_SCRIPT_ARGS=%s', repr(msvc.version), repr(script_args))

        if not script_args:
            return None

        if msvc.vs_def.vc_buildtools_def.vc_version_numeric < cls.VS2015.vc_buildtools_def.vc_version_numeric:
            debug(
                'invalid: msvc version constraint: %s < %s VS2015',
                repr(msvc.vs_def.vc_buildtools_def.vc_version_numeric),
                repr(cls.VS2015.vc_buildtools_def.vc_version_numeric)
            )
            err_msg = "MSVC_SCRIPT_ARGS ({}) constraint violation: MSVC_VERSION {} < {} VS2015".format(
                repr(script_args), repr(msvc.version), repr(cls.VS2015.vc_buildtools_def.vc_version)
            )
            raise MSVCArgumentError(err_msg)

        # user arguments are not validated
        argpair = (cls.SortOrder.USER, script_args)
        arglist.append(argpair)

        return script_args

    @classmethod
    def _user_script_argument_spectre(cls, env, spectre, user_argstr):

        matches = [m for m in cls.re_vcvars_spectre.finditer(user_argstr)]
        if not matches:
            return None

        if len(matches) > 1:
            debug('multiple spectre declarations: MSVC_SCRIPT_ARGS=%s', repr(user_argstr))
            err_msg = "multiple spectre declarations: MSVC_SCRIPT_ARGS={}".format(repr(user_argstr))
            raise MSVCArgumentError(err_msg)

        if not spectre:
            return None

        env_argstr = env.get('MSVC_SPECTRE_LIBS','')
        debug('multiple spectre declarations: MSVC_SPECTRE_LIBS=%s, MSVC_SCRIPT_ARGS=%s', repr(env_argstr), repr(user_argstr))

        err_msg = "multiple spectre declarations: MSVC_SPECTRE_LIBS={} and MSVC_SCRIPT_ARGS={}".format(
            repr(env_argstr), repr(user_argstr)
        )

        raise MSVCArgumentError(err_msg)

    @classmethod
    def msvc_script_arguments(cls, env, version, vc_dir, arg):

        arglist = []

        msvc = cls._msvc_version(version)

        if arg:
            argpair = (cls.SortOrder.ARCH, arg)
            arglist.append(argpair)

        if 'MSVC_SCRIPT_ARGS' in env:
            user_argstr = cls._msvc_script_argument_user(env, msvc, arglist)
        else:
            user_argstr = None

        if 'MSVC_UWP_APP' in env:
            uwp = cls._msvc_script_argument_uwp(env, msvc, arglist)
        else:
            uwp = None

        if user_argstr:
            cls._user_script_argument_uwp(env, uwp, user_argstr)

        platform_type = 'uwp' if uwp else 'desktop'

        if 'MSVC_SDK_VERSION' in env:
            sdk_version = cls._msvc_script_argument_sdk(env, msvc, platform_type, arglist)
        else:
            sdk_version = None

        if user_argstr:
            user_sdk = cls._user_script_argument_sdk(env, sdk_version, user_argstr)
        else:
            user_sdk = None

        if cls.MSVC_FORCE_DEFAULT_SDK:
            if not sdk_version and not user_sdk:
                sdk_version = cls._msvc_script_default_sdk(env, msvc, platform_type, arglist)

        if 'MSVC_TOOLSET_VERSION' in env:
            toolset_version = cls._msvc_script_argument_toolset(env, msvc, vc_dir, arglist)
        else:
            toolset_version = None

        if user_argstr:
            user_toolset = cls._user_script_argument_toolset(env, toolset_version, user_argstr)
        else:
            user_toolset = None

        if cls.MSVC_FORCE_DEFAULT_TOOLSET:
            if not toolset_version and not user_toolset:
                toolset_version = cls._msvc_script_default_toolset(env, msvc, vc_dir, arglist)

        if 'MSVC_SPECTRE_LIBS' in env:
            spectre = cls._msvc_script_argument_spectre(env, msvc, arglist)
        else:
            spectre = None

        if user_argstr:
            cls._user_script_argument_spectre(env, spectre, user_argstr)

        if arglist:
            arglist.sort()
            argstr = ' '.join([argpair[-1] for argpair in arglist]).strip()
        else:
            argstr = ''

        debug('arguments: %s', repr(argstr))
        return argstr

    @classmethod
    def reset(cls):
        debug('reset %s', cls.__name__)
        cls._reset_toolset_cache()

    @classmethod
    def verify(cls):
        debug('verify %s', cls.__name__)
        cls._verify_re_sdk_dispatch_map()

_Dispatcher.register(_ScriptArguments)

# internal consistency check
_Dispatcher.verify()


# SCons/Tool/MSCommon/vcTests.py

#
# __COPYRIGHT__
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# from typing import Dict, Any

__revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"

import os
import os.path
import unittest

import SCons.Node.FS
import SCons.Warnings
import SCons.Tool.MSCommon.vc

import TestCmd

original = os.getcwd()

test = TestCmd.TestCmd(workdir='')

os.chdir(test.workpath(''))

MSVCUnsupportedHostArch = SCons.Tool.MSCommon.vc.MSVCUnsupportedHostArch
MSVCUnsupportedTargetArch = SCons.Tool.MSCommon.vc.MSVCUnsupportedTargetArch

MS_TOOLS_VERSION='1.1.1'

class VswhereTestCase(unittest.TestCase):
    @staticmethod
    def _createVSWhere(path):
        os.makedirs(os.path.dirname(path), exist_ok=True)
        with open(path, 'w') as f:
            f.write("Created:%s"%f)

    def testDefaults(self):
        """
        Verify that msvc_find_vswhere() find's files in the specified paths
        """
        # import pdb; pdb.set_trace()
        vswhere_dirs = [os.path.splitdrive(p)[1] for p in SCons.Tool.MSCommon.vc.VSWHERE_PATHS]
        base_dir = test.workpath('fake_vswhere')
        test_vswhere_dirs = [os.path.join(base_dir,d[1:]) for d in  vswhere_dirs]

        SCons.Tool.MSCommon.vc.VSWHERE_PATHS = test_vswhere_dirs
        for vsw in test_vswhere_dirs:
            VswhereTestCase._createVSWhere(vsw)
            find_path = SCons.Tool.MSCommon.vc.msvc_find_vswhere()
            self.assertTrue(vsw == find_path, "Didn't find vswhere in %s found in %s"%(vsw, find_path))
            os.remove(vsw)

    # def specifiedVswherePathTest(self):
    #     "Verify that msvc.generate() respects VSWHERE Specified"


    


class MSVcTestCase(unittest.TestCase):

    @staticmethod
    def _createDummyCl(path, add_bin=True):
        """
        Creates a dummy cl.exe in the correct directory. 
        It will create all missing parent directories as well

        Args:
            path: Relative path to cl.exe for the version about to be tested.
        """

        # print("PATH:%s"%path)

        path = path.replace('\\', os.sep)
        if add_bin:
            create_path = os.path.join(path,'bin')
        else:
            create_path = path
        if create_path and not os.path.isdir(create_path):
            os.makedirs(create_path)
        
        create_this = os.path.join(create_path,'cl.exe')

        # print("Creating: %s"%create_this)
        with open(create_this,'w') as ct:
            ct.write('created')

        
    def runTest(self):
        """
        Check that all proper HOST_PLATFORM and TARGET_PLATFORM are handled.
        Verify that improper HOST_PLATFORM and/or TARGET_PLATFORM are properly handled.
        by SCons.Tool.MSCommon.vc._check_cl_exists_in_vc_dir()
        """

        check = SCons.Tool.MSCommon.vc._check_cl_exists_in_vc_dir

        env={'TARGET_ARCH':'x86'}
        _, clpathcomps = SCons.Tool.MSCommon.vc._LE2015_HOST_TARGET_BATCHARG_CLPATHCOMPS[('x86','x86')]
        path = os.path.join('.', *clpathcomps)
        MSVcTestCase._createDummyCl(path, add_bin=False)
 
        # print("retval:%s"%check(env, '.', '8.0'))


        # Setup for 14.1 (VS2017) and later tests

        # Create the VC minor/major version file
        tools_version_file = SCons.Tool.MSCommon.vc._VC_TOOLS_VERSION_FILE
        tools_dir = os.path.dirname(tools_version_file)
        if not os.path.isdir(tools_dir):
            os.makedirs(tools_dir)
        try:
            with open(tools_version_file, 'w') as tf:
                tf.write(MS_TOOLS_VERSION)
        except IOError as e:
            print("Failed trying to write :%s :%s"%(tools_version_file, e))


        # Now walk all the valid combinations of host/target for 14.1 (VS2017) and later
        vc_ge2017_list = SCons.Tool.MSCommon.vc._GE2017_HOST_TARGET_CFG.all_pairs

        for host, target in vc_ge2017_list:
            batfile, clpathcomps = SCons.Tool.MSCommon.vc._GE2017_HOST_TARGET_BATCHFILE_CLPATHCOMPS[(host,target)]
            # print("GT 14 Got: (%s, %s) -> (%s, %s)"%(host,target,batfile,clpathcomps))

            env={'TARGET_ARCH':target, 'HOST_ARCH':host}
            path = os.path.join('.', 'Tools', 'MSVC', MS_TOOLS_VERSION, *clpathcomps)
            MSVcTestCase._createDummyCl(path, add_bin=False)
            result=check(env, '.', '14.1')
            # print("for:(%s, %s) got :%s"%(host, target, result))
            self.assertTrue(result, "Checking host: %s target: %s" % (host, target))

        # Now test bogus value for HOST_ARCH
        env={'TARGET_ARCH':'x86', 'HOST_ARCH':'GARBAGE'}
        try:
            result=check(env, '.', '14.1')
            # print("for:%s got :%s"%(env, result))
            self.assertFalse(result, "Did not fail with bogus HOST_ARCH host: %s target: %s" % (env['HOST_ARCH'], env['TARGET_ARCH']))
        except MSVCUnsupportedHostArch:
            pass
        else:
            self.fail('Did not fail when HOST_ARCH specified as: %s'%env['HOST_ARCH'])

        # Now test bogus value for TARGET_ARCH
        env={'TARGET_ARCH':'GARBAGE', 'HOST_ARCH':'x86'}
        try:
            result=check(env, '.', '14.1')
            # print("for:%s got :%s"%(env, result))
            self.assertFalse(result, "Did not fail with bogus TARGET_ARCH host: %s target: %s" % (env['HOST_ARCH'], env['TARGET_ARCH']))
        except MSVCUnsupportedTargetArch:
            pass
        else:
            self.fail('Did not fail when TARGET_ARCH specified as: %s' % env['TARGET_ARCH'])

        # Test 14.0 (VS2015) to 8.0 (VS2005) versions
        vc_le2015_list = SCons.Tool.MSCommon.vc._LE2015_HOST_TARGET_CFG.all_pairs

        for host, target in vc_le2015_list:
            batarg, clpathcomps = SCons.Tool.MSCommon.vc._LE2015_HOST_TARGET_BATCHARG_CLPATHCOMPS[(host, target)]
            # print("LE 14 Got: (%s, %s) -> (%s, %s)"%(host,target,batarg,clpathcomps))
            env={'TARGET_ARCH':target, 'HOST_ARCH':host}
            path = os.path.join('.', *clpathcomps)
            MSVcTestCase._createDummyCl(path, add_bin=False)
            result=check(env, '.', '9.0')
            # print("for:(%s, %s) got :%s"%(host, target, result))
            self.assertTrue(result, "Checking host: %s target: %s" % (host, target))

        # Now test bogus value for HOST_ARCH
        env={'TARGET_ARCH':'x86', 'HOST_ARCH':'GARBAGE'}
        try:
            result=check(env, '.', '9.0')
            # print("for:%s got :%s"%(env, result))
            self.assertFalse(result, "Did not fail with bogus HOST_ARCH host: %s target: %s"%(env['HOST_ARCH'], env['TARGET_ARCH']))
        except MSVCUnsupportedHostArch:
            pass
        else:
            self.fail('Did not fail when HOST_ARCH specified as: %s' % env['HOST_ARCH'])

        # Now test bogus value for TARGET_ARCH
        env={'TARGET_ARCH':'GARBAGE', 'HOST_ARCH':'x86'}
        try:
            result=check(env, '.', '9.0')
            # print("for:%s got :%s"%(env, result))
            self.assertFalse(result, "Did not fail with bogus TARGET_ARCH host: %s target: %s"%(env['HOST_ARCH'], env['TARGET_ARCH']))
        except MSVCUnsupportedTargetArch:
            pass
        else:
            self.fail('Did not fail when TARGET_ARCH specified as: %s' % env['TARGET_ARCH'])

        # Test 7.1 (VS2003) and earlier
        vc_le2003_list = SCons.Tool.MSCommon.vc._LE2003_HOST_TARGET_CFG.all_pairs

        for host, target in vc_le2003_list:
            # print("LE 7.1 Got: (%s, %s)"%(host,target))
            env={'TARGET_ARCH':target, 'HOST_ARCH':host}
            path = os.path.join('.')
            MSVcTestCase._createDummyCl(path)
            result=check(env, '.', '6.0')
            # print("for:(%s, %s) got :%s"%(host, target, result))
            self.assertTrue(result, "Checking host: %s target: %s" % (host, target))

        # Now test bogus value for HOST_ARCH
        env={'TARGET_ARCH':'x86', 'HOST_ARCH':'GARBAGE'}
        try:
            result=check(env, '.', '6.0')
            # print("for:%s got :%s"%(env, result))
            self.assertFalse(result, "Did not fail with bogus HOST_ARCH host: %s target: %s" % (env['HOST_ARCH'], env['TARGET_ARCH']))
        except MSVCUnsupportedHostArch:
            pass
        else:
            self.fail('Did not fail when HOST_ARCH specified as: %s' % env['HOST_ARCH'])

        # Now test bogus value for TARGET_ARCH
        env={'TARGET_ARCH':'GARBAGE', 'HOST_ARCH':'x86'}
        try:
            result=check(env, '.', '6.0')
            # print("for:%s got :%s"%(env, result))
            self.assertFalse(result, "Did not fail with bogus TARGET_ARCH host: %s target: %s" % (env['HOST_ARCH'], env['TARGET_ARCH']))
        except MSVCUnsupportedTargetArch:
            pass
        else:
            self.fail('Did not fail when TARGET_ARCH specified as: %s' % env['TARGET_ARCH'])



if __name__ == "__main__":
    unittest.main()


# SCons/Tool/MSCommon/vs.py

# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""
MS Compilers: detect Visual Studio and/or Visual C/C++
"""

import os

import SCons.Errors
import SCons.Tool.MSCommon.vc
import SCons.Util

from .common import (
    debug,
    get_output,
    is_win64,
    normalize_env,
    parse_output,
    read_reg,
)


class VisualStudio:
    """
    An abstract base class for trying to find installed versions of
    Visual Studio.
    """
    def __init__(self, version, **kw):
        self.version = version
        kw['vc_version']  = kw.get('vc_version', version)
        kw['sdk_version'] = kw.get('sdk_version', version)
        self.__dict__.update(kw)
        self._cache = {}

    def find_batch_file(self):
        vs_dir = self.get_vs_dir()
        if not vs_dir:
            debug('no vs_dir')
            return None
        batch_file = os.path.join(vs_dir, self.batch_file_path)
        batch_file = os.path.normpath(batch_file)
        if not os.path.isfile(batch_file):
            debug('%s not on file system', batch_file)
            return None
        return batch_file

    def find_vs_dir_by_vc(self, env):
        dir = SCons.Tool.MSCommon.vc.find_vc_pdir(env, self.vc_version)
        if not dir:
            debug('no installed VC %s', self.vc_version)
            return None
        return os.path.abspath(os.path.join(dir, os.pardir))

    def find_vs_dir_by_reg(self, env):
        root = 'Software\\'

        if is_win64():
            root = root + 'Wow6432Node\\'
        for key in self.hkeys:
            if key=='use_dir':
                return self.find_vs_dir_by_vc(env)
            key = root + key
            try:
                comps = read_reg(key)
            except OSError:
                debug('no VS registry key %s', repr(key))
            else:
                debug('found VS in registry: %s', comps)
                return comps
        return None

    def find_vs_dir(self, env):
        """ Can use registry or location of VC to find vs dir
        First try to find by registry, and if that fails find via VC dir
        """

        vs_dir = self.find_vs_dir_by_reg(env)
        if not vs_dir:
            vs_dir = self.find_vs_dir_by_vc(env)
        debug('found VS in %s', str(vs_dir))
        return vs_dir

    def find_executable(self, env):
        vs_dir = self.get_vs_dir(env)
        if not vs_dir:
            debug('no vs_dir (%s)', vs_dir)
            return None
        executable = os.path.join(vs_dir, self.executable_path)
        executable = os.path.normpath(executable)
        if not os.path.isfile(executable):
            debug('%s not on file system', executable)
            return None
        return executable

    def get_batch_file(self):
        try:
            return self._cache['batch_file']
        except KeyError:
            batch_file = self.find_batch_file()
            self._cache['batch_file'] = batch_file
            return batch_file

    def get_executable(self, env=None):
        try:
            debug('using cache:%s', self._cache['executable'])
            return self._cache['executable']
        except KeyError:
            executable = self.find_executable(env)
            self._cache['executable'] = executable
            debug('not in cache:%s', executable)
            return executable

    def get_vs_dir(self, env=None):
        try:
            return self._cache['vs_dir']
        except KeyError:
            vs_dir = self.find_vs_dir(env)
            self._cache['vs_dir'] = vs_dir
            return vs_dir

    def get_supported_arch(self):
        try:
            return self._cache['supported_arch']
        except KeyError:
            # RDEVE: for the time being use hardcoded lists
            # supported_arch = self.find_supported_arch()
            self._cache['supported_arch'] = self.supported_arch
            return self.supported_arch

    def reset(self):
        self._cache = {}

# The list of supported Visual Studio versions we know how to detect.
#
# How to look for .bat file ?
#  - VS 2008 Express (x86):
#     * from registry key productdir, gives the full path to vsvarsall.bat. In
#     HKEY_LOCAL_MACHINE):
#         Software\Microsoft\VCEpress\9.0\Setup\VC\productdir
#     * from environmnent variable VS90COMNTOOLS: the path is then ..\..\VC
#     relatively to the path given by the variable.
#
#  - VS 2008 Express (WoW6432: 32 bits on windows x64):
#         Software\Wow6432Node\Microsoft\VCEpress\9.0\Setup\VC\productdir
#
#  - VS 2005 Express (x86):
#     * from registry key productdir, gives the full path to vsvarsall.bat. In
#     HKEY_LOCAL_MACHINE):
#         Software\Microsoft\VCEpress\8.0\Setup\VC\productdir
#     * from environmnent variable VS80COMNTOOLS: the path is then ..\..\VC
#     relatively to the path given by the variable.
#
#  - VS 2005 Express (WoW6432: 32 bits on windows x64): does not seem to have a
#  productdir ?
#
#  - VS 2003 .Net (pro edition ? x86):
#     * from registry key productdir. The path is then ..\Common7\Tools\
#     relatively to the key. The key is in HKEY_LOCAL_MACHINE):
#         Software\Microsoft\VisualStudio\7.1\Setup\VC\productdir
#     * from environmnent variable VS71COMNTOOLS: the path is the full path to
#     vsvars32.bat
#
#  - VS 98 (VS 6):
#     * from registry key productdir. The path is then Bin
#     relatively to the key. The key is in HKEY_LOCAL_MACHINE):
#         Software\Microsoft\VisualStudio\6.0\Setup\VC98\productdir
#
# The first version found in the list is the one used by default if
# there are multiple versions installed.  Barring good reasons to
# the contrary, this means we should list versions from most recent
# to oldest.  Pro versions get listed before Express versions on the
# assumption that, by default, you'd rather use the version you paid
# good money for in preference to whatever Microsoft makes available
# for free.
#
# If you update this list, update _VCVER and _VCVER_TO_PRODUCT_DIR in
# Tool/MSCommon/vc.py, and the MSVC_VERSION documentation in Tool/msvc.xml.

SupportedVSList = [
    # Visual Studio 2022
    VisualStudio('14.3',
                 vc_version='14.3',
                 sdk_version='10.0A',
                 hkeys=[],
                 common_tools_var='VS170COMNTOOLS',
                 executable_path=r'Common7\IDE\devenv.com',
                 # should be a fallback, prefer use vswhere installationPath
                 batch_file_path=r'Common7\Tools\VsDevCmd.bat',
                 supported_arch=['x86', 'amd64', "arm"],
                 ),

    # Visual Studio 2019
    VisualStudio('14.2',
                 vc_version='14.2',
                 sdk_version='10.0A',
                 hkeys=[],
                 common_tools_var='VS160COMNTOOLS',
                 executable_path=r'Common7\IDE\devenv.com',
                 # should be a fallback, prefer use vswhere installationPath
                 batch_file_path=r'Common7\Tools\VsDevCmd.bat',
                 supported_arch=['x86', 'amd64', "arm"],
                 ),

    # Visual Studio 2017
    VisualStudio('14.1',
                 vc_version='14.1',
                 sdk_version='10.0A',
                 hkeys=[],
                 common_tools_var='VS150COMNTOOLS',
                 executable_path=r'Common7\IDE\devenv.com',
                 # should be a fallback, prefer use vswhere installationPath
                 batch_file_path=r'Common7\Tools\VsDevCmd.bat',
                 supported_arch=['x86', 'amd64', "arm"],
                 ),

    # Visual C++ 2017 Express Edition (for Desktop)
    VisualStudio('14.1Exp',
                 vc_version='14.1',
                 sdk_version='10.0A',
                 hkeys=[],
                 common_tools_var='VS150COMNTOOLS',
                 executable_path=r'Common7\IDE\WDExpress.exe',
                 # should be a fallback, prefer use vswhere installationPath
                 batch_file_path=r'Common7\Tools\VsDevCmd.bat',
                 supported_arch=['x86', 'amd64', "arm"],
    ),

    # Visual Studio 2015
    VisualStudio('14.0',
                 vc_version='14.0',
                 sdk_version='10.0',
                 hkeys=[r'Microsoft\VisualStudio\14.0\Setup\VS\ProductDir'],
                 common_tools_var='VS140COMNTOOLS',
                 executable_path=r'Common7\IDE\devenv.com',
                 batch_file_path=r'Common7\Tools\vsvars32.bat',
                 supported_arch=['x86', 'amd64', "arm"],
    ),

    # Visual C++ 2015 Express Edition (for Desktop)
    VisualStudio('14.0Exp',
                 vc_version='14.0',
                 sdk_version='10.0A',
                 hkeys=[r'Microsoft\VisualStudio\14.0\Setup\VS\ProductDir'],
                 common_tools_var='VS140COMNTOOLS',
                 executable_path=r'Common7\IDE\WDExpress.exe',
                 batch_file_path=r'Common7\Tools\vsvars32.bat',
                 supported_arch=['x86', 'amd64', "arm"],
    ),

    # Visual Studio 2013
    VisualStudio('12.0',
                 vc_version='12.0',
                 sdk_version='8.1A',
                 hkeys=[r'Microsoft\VisualStudio\12.0\Setup\VS\ProductDir'],
                 common_tools_var='VS120COMNTOOLS',
                 executable_path=r'Common7\IDE\devenv.com',
                 batch_file_path=r'Common7\Tools\vsvars32.bat',
                 supported_arch=['x86', 'amd64'],
    ),

    # Visual C++ 2013 Express Edition (for Desktop)
    VisualStudio('12.0Exp',
                 vc_version='12.0',
                 sdk_version='8.1A',
                 hkeys=[r'Microsoft\VisualStudio\12.0\Setup\VS\ProductDir'],
                 common_tools_var='VS120COMNTOOLS',
                 executable_path=r'Common7\IDE\WDExpress.exe',
                 batch_file_path=r'Common7\Tools\vsvars32.bat',
                 supported_arch=['x86', 'amd64'],
    ),

    # Visual Studio 2012
    VisualStudio('11.0',
                 sdk_version='8.0A',
                 hkeys=[r'Microsoft\VisualStudio\11.0\Setup\VS\ProductDir'],
                 common_tools_var='VS110COMNTOOLS',
                 executable_path=r'Common7\IDE\devenv.com',
                 batch_file_path=r'Common7\Tools\vsvars32.bat',
                 supported_arch=['x86', 'amd64'],
    ),

    # Visual C++ 2012 Express Edition (for Desktop)
    VisualStudio('11.0Exp',
                 vc_version='11.0',
                 sdk_version='8.0A',
                 hkeys=[r'Microsoft\VisualStudio\11.0\Setup\VS\ProductDir'],
                 common_tools_var='VS110COMNTOOLS',
                 executable_path=r'Common7\IDE\WDExpress.exe',
                 batch_file_path=r'Common7\Tools\vsvars32.bat',
                 supported_arch=['x86', 'amd64'],
    ),

    # Visual Studio 2010
    VisualStudio('10.0',
                 sdk_version='7.0A',
                 hkeys=[r'Microsoft\VisualStudio\10.0\Setup\VS\ProductDir'],
                 common_tools_var='VS100COMNTOOLS',
                 executable_path=r'Common7\IDE\devenv.com',
                 batch_file_path=r'Common7\Tools\vsvars32.bat',
                 supported_arch=['x86', 'amd64'],
    ),

    # Visual C++ 2010 Express Edition
    VisualStudio('10.0Exp',
                 vc_version='10.0',
                 sdk_version='7.0A',
                 hkeys=[r'Microsoft\VCExpress\10.0\Setup\VS\ProductDir'],
                 common_tools_var='VS100COMNTOOLS',
                 executable_path=r'Common7\IDE\VCExpress.exe',
                 batch_file_path=r'Common7\Tools\vsvars32.bat',
                 supported_arch=['x86'],
    ),

    # Visual Studio 2008
    VisualStudio('9.0',
                 sdk_version='6.0A',
                 hkeys=[r'Microsoft\VisualStudio\9.0\Setup\VS\ProductDir'],
                 common_tools_var='VS90COMNTOOLS',
                 executable_path=r'Common7\IDE\devenv.com',
                 batch_file_path=r'Common7\Tools\vsvars32.bat',
                 supported_arch=['x86', 'amd64'],
    ),

    # Visual C++ 2008 Express Edition
    VisualStudio('9.0Exp',
                 vc_version='9.0',
                 sdk_version='6.0A',
                 hkeys=[r'Microsoft\VCExpress\9.0\Setup\VS\ProductDir'],
                 common_tools_var='VS90COMNTOOLS',
                 executable_path=r'Common7\IDE\VCExpress.exe',
                 batch_file_path=r'Common7\Tools\vsvars32.bat',
                 supported_arch=['x86'],
    ),

    # Visual Studio 2005
    VisualStudio('8.0',
                 sdk_version='6.0A',
                 hkeys=[r'Microsoft\VisualStudio\8.0\Setup\VS\ProductDir'],
                 common_tools_var='VS80COMNTOOLS',
                 executable_path=r'Common7\IDE\devenv.com',
                 batch_file_path=r'Common7\Tools\vsvars32.bat',
                 default_dirname='Microsoft Visual Studio 8',
                 supported_arch=['x86', 'amd64'],
    ),

    # Visual C++ 2005 Express Edition
    VisualStudio('8.0Exp',
                 vc_version='8.0Exp',
                 sdk_version='6.0A',
                 hkeys=[r'Microsoft\VCExpress\8.0\Setup\VS\ProductDir'],
                 common_tools_var='VS80COMNTOOLS',
                 executable_path=r'Common7\IDE\VCExpress.exe',
                 batch_file_path=r'Common7\Tools\vsvars32.bat',
                 default_dirname='Microsoft Visual Studio 8',
                 supported_arch=['x86'],
    ),

    # Visual Studio .NET 2003
    VisualStudio('7.1',
                 sdk_version='6.0',
                 hkeys=[r'Microsoft\VisualStudio\7.1\Setup\VS\ProductDir'],
                 common_tools_var='VS71COMNTOOLS',
                 executable_path=r'Common7\IDE\devenv.com',
                 batch_file_path=r'Common7\Tools\vsvars32.bat',
                 default_dirname='Microsoft Visual Studio .NET 2003',
                 supported_arch=['x86'],
    ),

    # Visual Studio .NET
    VisualStudio('7.0',
                 sdk_version='2003R2',
                 hkeys=[r'Microsoft\VisualStudio\7.0\Setup\VS\ProductDir'],
                 common_tools_var='VSCOMNTOOLS',
                 executable_path=r'Common7\IDE\devenv.com',
                 batch_file_path=r'Common7\Tools\vsvars32.bat',
                 default_dirname='Microsoft Visual Studio .NET',
                 supported_arch=['x86'],
    ),

    # Visual Studio 6.0
    VisualStudio('6.0',
                 sdk_version='2003R1',
                 hkeys=[r'Microsoft\VisualStudio\6.0\Setup\Microsoft Visual Studio\ProductDir',
                        'use_dir'],
                 common_tools_var='MSDevDir',
                 executable_path=r'Common\MSDev98\Bin\MSDEV.COM',
                 batch_file_path=r'Common7\Tools\vsvars32.bat',
                 default_dirname='Microsoft Visual Studio',
                 supported_arch=['x86'],
    ),
]

SupportedVSMap = {}
for vs in SupportedVSList:
    SupportedVSMap[vs.version] = vs


# Finding installed versions of Visual Studio isn't cheap, because it
# goes not only to the registry but also to the disk to sanity-check
# that there is, in fact, a Visual Studio directory there and that the
# registry entry isn't just stale.  Find this information once, when
# requested, and cache it.

InstalledVSList = None
InstalledVSMap  = None

def get_installed_visual_studios(env=None):
    global InstalledVSList
    global InstalledVSMap
    if InstalledVSList is None:
        InstalledVSList = []
        InstalledVSMap = {}
        for vs in SupportedVSList:
            debug('trying to find VS %s', vs.version)
            if vs.get_executable(env):
                debug('found VS %s', vs.version)
                InstalledVSList.append(vs)
                InstalledVSMap[vs.version] = vs
    return InstalledVSList

def reset_installed_visual_studios():
    global InstalledVSList
    global InstalledVSMap
    InstalledVSList = None
    InstalledVSMap  = None
    for vs in SupportedVSList:
        vs.reset()

    # Need to clear installed VC's as well as they are used in finding
    # installed VS's
    SCons.Tool.MSCommon.vc.reset_installed_vcs()


# We may be asked to update multiple construction environments with
# SDK information.  When doing this, we check on-disk for whether
# the SDK has 'mfc' and 'atl' subdirectories.  Since going to disk
# is expensive, cache results by directory.

#SDKEnvironmentUpdates = {}
#
#def set_sdk_by_directory(env, sdk_dir):
#    global SDKEnvironmentUpdates
#    try:
#        env_tuple_list = SDKEnvironmentUpdates[sdk_dir]
#    except KeyError:
#        env_tuple_list = []
#        SDKEnvironmentUpdates[sdk_dir] = env_tuple_list
#
#        include_path = os.path.join(sdk_dir, 'include')
#        mfc_path = os.path.join(include_path, 'mfc')
#        atl_path = os.path.join(include_path, 'atl')
#
#        if os.path.exists(mfc_path):
#            env_tuple_list.append(('INCLUDE', mfc_path))
#        if os.path.exists(atl_path):
#            env_tuple_list.append(('INCLUDE', atl_path))
#        env_tuple_list.append(('INCLUDE', include_path))
#
#        env_tuple_list.append(('LIB', os.path.join(sdk_dir, 'lib')))
#        env_tuple_list.append(('LIBPATH', os.path.join(sdk_dir, 'lib')))
#        env_tuple_list.append(('PATH', os.path.join(sdk_dir, 'bin')))
#
#    for variable, directory in env_tuple_list:
#        env.PrependENVPath(variable, directory)

def msvs_exists(env=None) -> bool:
    return len(get_installed_visual_studios(env)) > 0

def get_vs_by_version(msvs):
    global InstalledVSMap
    global SupportedVSMap

    debug('called')
    if msvs not in SupportedVSMap:
        msg = "Visual Studio version %s is not supported" % repr(msvs)
        raise SCons.Errors.UserError(msg)
    get_installed_visual_studios()
    vs = InstalledVSMap.get(msvs)
    debug('InstalledVSMap:%s', InstalledVSMap)
    debug('found vs:%s', vs)
    # Some check like this would let us provide a useful error message
    # if they try to set a Visual Studio version that's not installed.
    # However, we also want to be able to run tests (like the unit
    # tests) on systems that don't, or won't ever, have it installed.
    # It might be worth resurrecting this, with some configurable
    # setting that the tests can use to bypass the check.
    #if not vs:
    #    msg = "Visual Studio version %s is not installed" % repr(msvs)
    #    raise SCons.Errors.UserError, msg
    return vs

def get_default_version(env):
    """Returns the default version string to use for MSVS.

    If no version was requested by the user through the MSVS environment
    variable, query all the available visual studios through
    get_installed_visual_studios, and take the highest one.

    Return
    ------
    version: str
        the default version.
    """
    if 'MSVS' not in env or not SCons.Util.is_Dict(env['MSVS']):
        # get all versions, and remember them for speed later
        versions = [vs.version for vs in get_installed_visual_studios()]
        env['MSVS'] = {'VERSIONS' : versions}
    else:
        versions = env['MSVS'].get('VERSIONS', [])

    if 'MSVS_VERSION' not in env:
        if versions:
            env['MSVS_VERSION'] = versions[0] #use highest version by default
        else:
            debug('WARNING: no installed versions found, '
                  'using first in SupportedVSList (%s)',
                  SupportedVSList[0].version)
            env['MSVS_VERSION'] = SupportedVSList[0].version

    env['MSVS']['VERSION'] = env['MSVS_VERSION']

    return env['MSVS_VERSION']

def get_default_arch(env):
    """Return the default arch to use for MSVS

    if no version was requested by the user through the MSVS_ARCH environment
    variable, select x86

    Return
    ------
    arch: str
    """
    arch = env.get('MSVS_ARCH', 'x86')

    msvs = InstalledVSMap.get(env['MSVS_VERSION'])

    if not msvs:
        arch = 'x86'
    elif arch not in msvs.get_supported_arch():
        fmt = "Visual Studio version %s does not support architecture %s"
        raise SCons.Errors.UserError(fmt % (env['MSVS_VERSION'], arch))

    return arch

def merge_default_version(env):
    version = get_default_version(env)
    arch = get_default_arch(env)

# TODO: refers to versions and arch which aren't defined; called nowhere. Drop?
def msvs_setup_env(env):
    msvs = get_vs_by_version(version)
    if msvs is None:
        return
    batfilename = msvs.get_batch_file()

    # XXX: I think this is broken. This will silently set a bogus tool instead
    # of failing, but there is no other way with the current scons tool
    # framework
    if batfilename is not None:

        vars = ('LIB', 'LIBPATH', 'PATH', 'INCLUDE')

        msvs_list = get_installed_visual_studios()
        vscommonvarnames = [vs.common_tools_var for vs in msvs_list]
        save_ENV = env['ENV']
        nenv = normalize_env(env['ENV'],
                             ['COMSPEC'] + vscommonvarnames,
                             force=True)
        try:
            output = get_output(batfilename, arch, env=nenv)
        finally:
            env['ENV'] = save_ENV
        vars = parse_output(output, vars)

        for k, v in vars.items():
            env.PrependENVPath(k, v, delete_existing=1)

def query_versions():
    """Query the system to get available versions of VS. A version is
    considered when a batfile is found."""
    msvs_list = get_installed_visual_studios()
    versions = [msvs.version for msvs in msvs_list]
    return versions

# Local Variables:
# tab-width:4
# indent-tabs-mode:nil
# End:
# vim: set expandtab tabstop=4 shiftwidth=4:


# SCons/Tool/MSCommon/MSVC/Config.py
# MIT License
#
# Copyright The SCons Foundation
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be included
# in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

"""
Constants and initialized data structures for Microsoft Visual C/C++.
"""

from collections import (
    namedtuple,
)

from . import Dispatcher

Dispatcher.register_modulename(__name__)

UNDEFINED = object()

BOOLEAN_SYMBOLS = {}
BOOLEAN_EXTERNAL = {}

for bool, symbol_list, symbol_case_list in [
    (False, (False, 0, '0', None, ''), ('False', 'No',  'F', 'N')),
    (True,  (True,  1, '1'),           ('True',  'Yes', 'T', 'Y')),
]:
    BOOLEAN_SYMBOLS[bool] = list(symbol_list)
    for symbol in symbol_case_list:
        BOOLEAN_SYMBOLS[bool].extend([symbol, symbol.lower(), symbol.upper()])

    for symbol in BOOLEAN_SYMBOLS[bool]:
        BOOLEAN_EXTERNAL[symbol] = bool

MSVC_RUNTIME_DEFINITION = namedtuple('MSVCRuntime', [
    'vc_runtime',
    'vc_runtime_numeric',
    'vc_runtime_alias_list',
    'vc_runtime_vsdef_list',
])

MSVC_RUNTIME_DEFINITION_LIST = []

MSVC_RUNTIME_INTERNAL = {}
MSVC_RUNTIME_EXTERNAL = {}

for vc_runtime, vc_runtime_numeric, vc_runtime_alias_list in [
    ('140', 140, ['ucrt']),
    ('120', 120, ['msvcr120']),
    ('110', 110, ['msvcr110']),
    ('100', 100, ['msvcr100']),
    ('90',   90, ['msvcr90']),
    ('80',   80, ['msvcr80']),
    ('71',   71, ['msvcr71']),
    ('70',   70, ['msvcr70']),
    ('60',   60, ['msvcrt']),
]:
    vc_runtime_def = MSVC_RUNTIME_DEFINITION(
        vc_runtime = vc_runtime,
        vc_runtime_numeric = vc_runtime_numeric,
        vc_runtime_alias_list = vc_runtime_alias_list,
        vc_runtime_vsdef_list = [],
    )

    MSVC_RUNTIME_DEFINITION_LIST.append(vc_runtime_def)

    MSVC_RUNTIME_INTERNAL[vc_runtime] = vc_runtime_def
    MSVC_RUNTIME_EXTERNAL[vc_runtime] = vc_runtime_def

    for vc_runtime_alias in vc_runtime_alias_list:
        MSVC_RUNTIME_EXTERNAL[vc_runtime_alias] = vc_runtime_def

MSVC_BUILDTOOLS_DEFINITION = namedtuple('MSVCBuildtools', [
    'vc_buildtools',
    'vc_buildtools_numeric',
    'vc_version',
    'vc_version_numeric',
    'cl_version',
    'cl_version_numeric',
    'vc_runtime_def',
])

MSVC_BUILDTOOLS_DEFINITION_LIST = []

MSVC_BUILDTOOLS_INTERNAL = {}
MSVC_BUILDTOOLS_EXTERNAL = {}

VC_VERSION_MAP = {}

for vc_buildtools, vc_version, cl_version, vc_runtime in [
    ('v143', '14.3', '19.3', '140'),
    ('v142', '14.2', '19.2', '140'),
    ('v141', '14.1', '19.1', '140'),
    ('v140', '14.0', '19.0', '140'),
    ('v120', '12.0', '18.0', '120'),
    ('v110', '11.0', '17.0', '110'),
    ('v100', '10.0', '16.0', '100'),
    ('v90',   '9.0', '15.0',  '90'),
    ('v80',   '8.0', '14.0',  '80'),
    ('v71',   '7.1', '13.1',  '71'),
    ('v70',   '7.0', '13.0',  '70'),
    ('v60',   '6.0', '12.0',  '60'),
]:

    vc_runtime_def = MSVC_RUNTIME_INTERNAL[vc_runtime]

    vc_buildtools_def = MSVC_BUILDTOOLS_DEFINITION(
        vc_buildtools = vc_buildtools,
        vc_buildtools_numeric = int(vc_buildtools[1:]),
        vc_version = vc_version,
        vc_version_numeric = float(vc_version),
        cl_version = cl_version,
        cl_version_numeric = float(cl_version),
        vc_runtime_def = vc_runtime_def,
    )

    MSVC_BUILDTOOLS_DEFINITION_LIST.append(vc_buildtools_def)

    MSVC_BUILDTOOLS_INTERNAL[vc_buildtools] = vc_buildtools_def
    MSVC_BUILDTOOLS_EXTERNAL[vc_buildtools] = vc_buildtools_def
    MSVC_BUILDTOOLS_EXTERNAL[vc_version] = vc_buildtools_def

    VC_VERSION_MAP[vc_version] = vc_buildtools_def

MSVS_VERSION_INTERNAL = {}
MSVS_VERSION_EXTERNAL = {}

MSVC_VERSION_INTERNAL = {}
MSVC_VERSION_EXTERNAL = {}

MSVS_VERSION_MAJOR_MAP = {}

CL_VERSION_MAP = {}

MSVC_SDK_VERSIONS = set()

VISUALSTUDIO_DEFINITION = namedtuple('VisualStudioDefinition', [
    'vs_product',
    'vs_product_alias_list',
    'vs_version',
    'vs_version_major',
    'vs_envvar',
    'vs_express',
    'vs_lookup',
    'vc_sdk_versions',
    'vc_ucrt_versions',
    'vc_uwp',
    'vc_buildtools_def',
    'vc_buildtools_all',
])

VISUALSTUDIO_DEFINITION_LIST = []

VS_PRODUCT_ALIAS = {
    '1998': ['6']
}

# vs_envvar: VisualStudioVersion defined in environment for MSVS 2012 and later
#            MSVS 2010 and earlier cl_version -> vs_def is a 1:1 mapping
# SDK attached to product or buildtools?
for vs_product, vs_version, vs_envvar, vs_express, vs_lookup, vc_sdk, vc_ucrt, vc_uwp, vc_buildtools_all in [
    ('2022', '17.0', True,  False, 'vswhere' , ['10.0', '8.1'], ['10'],   'uwp', ['v143', 'v142', 'v141', 'v140']),
    ('2019', '16.0', True,  False, 'vswhere' , ['10.0', '8.1'], ['10'],   'uwp', ['v142', 'v141', 'v140']),
    ('2017', '15.0', True,  True,  'vswhere' , ['10.0', '8.1'], ['10'],   'uwp', ['v141', 'v140']),
    ('2015', '14.0', True,  True,  'registry', ['10.0', '8.1'], ['10'], 'store', ['v140']),
    ('2013', '12.0', True,  True,  'registry',            None,   None,    None, ['v120']),
    ('2012', '11.0', True,  True,  'registry',            None,   None,    None, ['v110']),
    ('2010', '10.0', False, True,  'registry',            None,   None,    None, ['v100']),
    ('2008',  '9.0', False, True,  'registry',            None,   None,    None, ['v90']),
    ('2005',  '8.0', False, True,  'registry',            None,   None,    None, ['v80']),
    ('2003',  '7.1', False, False, 'registry',            None,   None,    None, ['v71']),
    ('2002',  '7.0', False, False, 'registry',            None,   None,    None, ['v70']),
    ('1998',  '6.0', False, False, 'registry',            None,   None,    None, ['v60']),
]:

    vs_version_major = vs_version.split('.')[0]

    vc_buildtools_def = MSVC_BUILDTOOLS_INTERNAL[vc_buildtools_all[0]]

    vs_def = VISUALSTUDIO_DEFINITION(
        vs_product = vs_product,
        vs_product_alias_list = [],
        vs_version = vs_version,
        vs_version_major = vs_version_major,
        vs_envvar = vs_envvar,
        vs_express = vs_express,
        vs_lookup = vs_lookup,
        vc_sdk_versions = vc_sdk,
        vc_ucrt_versions = vc_ucrt,
        vc_uwp = vc_uwp,
        vc_buildtools_def = vc_buildtools_def,
        vc_buildtools_all = vc_buildtools_all,
    )

    VISUALSTUDIO_DEFINITION_LIST.append(vs_def)

    vc_buildtools_def.vc_runtime_def.vc_runtime_vsdef_list.append(vs_def)

    MSVS_VERSION_INTERNAL[vs_product] = vs_def
    MSVS_VERSION_EXTERNAL[vs_product] = vs_def
    MSVS_VERSION_EXTERNAL[vs_version] = vs_def

    MSVC_VERSION_INTERNAL[vc_buildtools_def.vc_version] = vs_def
    MSVC_VERSION_EXTERNAL[vs_product] = vs_def
    MSVC_VERSION_EXTERNAL[vc_buildtools_def.vc_version] = vs_def
    MSVC_VERSION_EXTERNAL[vc_buildtools_def.vc_buildtools] = vs_def

    if vs_product in VS_PRODUCT_ALIAS:
        for vs_product_alias in VS_PRODUCT_ALIAS[vs_product]:
            vs_def.vs_product_alias_list.append(vs_product_alias)
            MSVS_VERSION_EXTERNAL[vs_product_alias] = vs_def
            MSVC_VERSION_EXTERNAL[vs_product_alias] = vs_def

    MSVS_VERSION_MAJOR_MAP[vs_version_major] = vs_def

    CL_VERSION_MAP[vc_buildtools_def.cl_version] = vs_def

    if not vc_sdk:
        continue

    MSVC_SDK_VERSIONS.update(vc_sdk)

# convert string version set to string version list ranked in descending order
MSVC_SDK_VERSIONS = [str(f) for f in sorted([float(s) for s in MSVC_SDK_VERSIONS], reverse=True)]

MSVS_VERSION_LEGACY = {}
MSVC_VERSION_LEGACY = {}

for vdict in (MSVS_VERSION_EXTERNAL, MSVC_VERSION_INTERNAL):
    for key, vs_def in vdict.items():
        if key not in MSVS_VERSION_LEGACY:
            MSVS_VERSION_LEGACY[key] = vs_def
            MSVC_VERSION_LEGACY[key] = vs_def

# MSVC_NOTFOUND_POLICY definition:
#     error:   raise exception
#     warning: issue warning and continue
#     ignore:  continue

MSVC_NOTFOUND_POLICY_DEFINITION = namedtuple('MSVCNotFoundPolicyDefinition', [
    'value',
    'symbol',
])

MSVC_NOTFOUND_DEFINITION_LIST = []

MSVC_NOTFOUND_POLICY_INTERNAL = {}
MSVC_NOTFOUND_POLICY_EXTERNAL = {}

for policy_value, policy_symbol_list in [
    (True,  ['Error',   'Exception']),
    (False, ['Warning', 'Warn']),
    (None,  ['Ignore',  'Suppress']),
]:

    policy_symbol = policy_symbol_list[0].lower()
    policy_def = MSVC_NOTFOUND_POLICY_DEFINITION(policy_value, policy_symbol)

    MSVC_NOTFOUND_DEFINITION_LIST.append(vs_def)

    MSVC_NOTFOUND_POLICY_INTERNAL[policy_symbol] = policy_def

    for policy_symbol in policy_symbol_list:
        MSVC_NOTFOUND_POLICY_EXTERNAL[policy_symbol.lower()] = policy_def
        MSVC_NOTFOUND_POLICY_EXTERNAL[policy_symbol] = policy_def
        MSVC_NOTFOUND_POLICY_EXTERNAL[policy_symbol.upper()] = policy_def

