diff options
Diffstat (limited to 'engine/SCons/Tool/msvs.py')
| -rw-r--r-- | engine/SCons/Tool/msvs.py | 195 | 
1 files changed, 132 insertions, 63 deletions
diff --git a/engine/SCons/Tool/msvs.py b/engine/SCons/Tool/msvs.py index afe4bef..38965e3 100644 --- a/engine/SCons/Tool/msvs.py +++ b/engine/SCons/Tool/msvs.py @@ -32,7 +32,7 @@ selection method.  from __future__ import print_function -__revision__ = "src/engine/SCons/Tool/msvs.py a56bbd8c09fb219ab8a9673330ffcd55279219d0 2019-03-26 23:16:31 bdeegan" +__revision__ = "src/engine/SCons/Tool/msvs.py e724ae812eb96f4858a132f5b8c769724744faf6 2019-07-21 00:04:47 bdeegan"  import SCons.compat @@ -70,10 +70,14 @@ def xmlify(s):      s = s.replace('\n', '
')      return s -# Process a CPPPATH list in includes, given the env, target and source. -# Returns a tuple of nodes.  def processIncludes(includes, env, target, source): -    return SCons.PathList.PathList(includes).subst_path(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)]  external_makefile_guid = '{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}' @@ -348,10 +352,20 @@ V10DebugSettings = {  }  class _GenerateV10User(_UserGenerator): -    """Generates a Project'user file for MSVS 2010""" +    """Generates a Project'user file for MSVS 2010 or later"""      def __init__(self, dspfile, source, env): -        self.versionstr = '4.0' +        version_num, suite = msvs_parse_version(env['MSVS_VERSION']) +        if 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 @@ -397,7 +411,7 @@ class _DSPGenerator(object):          elif SCons.Util.is_List(env['variant']):              variants = env['variant'] -        if 'buildtarget' not in env or env['buildtarget'] == None: +        if 'buildtarget' not in env or env['buildtarget'] is None:              buildtarget = ['']          elif SCons.Util.is_String(env['buildtarget']):              buildtarget = [env['buildtarget']] @@ -418,7 +432,7 @@ class _DSPGenerator(object):              for _ in variants:                  buildtarget.append(bt) -        if 'outdir' not in env or env['outdir'] == None: +        if 'outdir' not in env or env['outdir'] is None:              outdir = ['']          elif SCons.Util.is_String(env['outdir']):              outdir = [env['outdir']] @@ -439,7 +453,7 @@ class _DSPGenerator(object):              for v in variants:                  outdir.append(s) -        if 'runfile' not in env or env['runfile'] == None: +        if 'runfile' not in env or env['runfile'] is None:              runfile = buildtarget[-1:]          elif SCons.Util.is_String(env['runfile']):              runfile = [env['runfile']] @@ -462,15 +476,41 @@ class _DSPGenerator(object):          self.sconscript = env['MSVSSCONSCRIPT'] -        if 'cmdargs' not in env or env['cmdargs'] == None: -            cmdargs = [''] * len(variants) -        elif SCons.Util.is_String(env['cmdargs']): -            cmdargs = [env['cmdargs']] * len(variants) -        elif SCons.Util.is_List(env['cmdargs']): -            if len(env['cmdargs']) != len(variants): -                raise SCons.Errors.InternalError("Sizes of 'cmdargs' and 'variant' lists must be the same.") +        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: -                cmdargs = env['cmdargs'] +                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)          self.env = env @@ -513,13 +553,17 @@ class _DSPGenerator(object):          for n in sourcenames:              self.sources[n].sort(key=lambda a: a.lower()) -        def AddConfig(self, variant, buildtarget, outdir, runfile, cmdargs, dspfile=dspfile): +        def AddConfig(self, variant, buildtarget, outdir, runfile, cmdargs, cppdefines, cpppaths, dspfile=dspfile, env=env):              config = Config()              config.buildtarget = buildtarget              config.outdir = outdir              config.cmdargs = cmdargs +            config.cppdefines = cppdefines              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) @@ -532,12 +576,12 @@ class _DSPGenerator(object):              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]) +            AddConfig(self, variants[i], buildtarget[i], outdir[i], runfile[i], cmdargs[i], cppdefines[i], cpppaths[i])          self.platforms = []          for key in list(self.configs.keys()):              platform = self.configs[key].platform -            if not platform in self.platforms: +            if platform not in self.platforms:                  self.platforms.append(platform)      def Build(self): @@ -553,16 +597,16 @@ V6DSPHeader = """\  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  !MESSAGE NMAKE /f "%(name)s.mak". -!MESSAGE  +!MESSAGE  !MESSAGE You can specify a configuration when running NMAKE  !MESSAGE by defining the macro CFG on the command line. For example: -!MESSAGE  +!MESSAGE  !MESSAGE NMAKE /f "%(name)s.mak" CFG="%(name)s - Win32 %(confkey)s" -!MESSAGE  +!MESSAGE  !MESSAGE Possible choices for configuration are: -!MESSAGE  +!MESSAGE  """  class _GenerateV6DSP(_DSPGenerator): @@ -580,7 +624,7 @@ class _GenerateV6DSP(_DSPGenerator):          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') +        self.file.write('!MESSAGE\n\n')      def PrintProject(self):          name = self.name @@ -637,7 +681,7 @@ class _GenerateV6DSP(_DSPGenerator):                  first = 1              else:                  self.file.write('!ELSEIF  "$(CFG)" == "%s - Win32 %s"\n\n' % (name,kind)) -        self.file.write('!ENDIF \n\n') +        self.file.write('!ENDIF\n\n')          self.PrintSourceFiles()          self.file.write('# End Target\n'                          '# End Project\n') @@ -645,10 +689,10 @@ class _GenerateV6DSP(_DSPGenerator):          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.encodestring(pdata).decode() +            pdata = base64.b64encode(pdata).decode()              self.file.write(pdata + '\n')              pdata = pickle.dumps(self.sources,PICKLE_PROTOCOL) -            pdata = base64.encodestring(pdata).decode() +            pdata = base64.b64encode(pdata).decode()              self.file.write(pdata + '\n')      def PrintSourceFiles(self): @@ -685,11 +729,13 @@ class _GenerateV6DSP(_DSPGenerator):              return # doesn't exist yet, so can't add anything to configs.          line = dspfile.readline() +        # skip until marker          while line:              if line.find("# End Project") > -1:                  break              line = dspfile.readline() +        # read to get configs          line = dspfile.readline()          datas = line          while line and line != '\n': @@ -707,12 +753,14 @@ class _GenerateV6DSP(_DSPGenerator):          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. @@ -878,6 +926,8 @@ class _GenerateV7DSP(_DSPGenerator, _GenerateV7User):              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: @@ -895,9 +945,8 @@ class _GenerateV7DSP(_DSPGenerator, _GenerateV7User):              # 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(self.env.get('CPPDEFINES', [])))) -            includepath_Dirs = processIncludes(self.env.get('CPPPATH', []), self.env, None, None) -            includepath = xmlify(';'.join([str(x) for x in includepath_Dirs])) +            preprocdefs = xmlify(';'.join(processDefines(cppdefines))) +            includepath = xmlify(';'.join(processIncludes(cpppaths, self.env, None, None)))              if not env_has_buildtarget:                  del self.env['MSVSBUILDTARGET'] @@ -917,10 +966,10 @@ class _GenerateV7DSP(_DSPGenerator, _GenerateV7User):          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.encodestring(pdata).decode() +            pdata = base64.b64encode(pdata).decode()              self.file.write('<!-- SCons Data:\n' + pdata + '\n')              pdata = pickle.dumps(self.sources,PICKLE_PROTOCOL) -            pdata = base64.encodestring(pdata).decode() +            pdata = base64.b64encode(pdata).decode()              self.file.write(pdata + '-->\n')      def printSources(self, hierarchy, commonprefix): @@ -998,11 +1047,13 @@ class _GenerateV7DSP(_DSPGenerator, _GenerateV7User):              return # doesn't exist yet, so can't add anything to configs.          line = dspfile.readline() +        # skip until marker          while line:              if line.find('<!-- SCons Data:') > -1:                  break              line = dspfile.readline() +        # read to get configs          line = dspfile.readline()          datas = line          while line and line != '\n': @@ -1020,12 +1071,14 @@ class _GenerateV7DSP(_DSPGenerator, _GenerateV7User):          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: @@ -1052,7 +1105,7 @@ class _GenerateV7DSP(_DSPGenerator, _GenerateV7User):  V10DSPHeader = """\  <?xml version="1.0" encoding="%(encoding)s"?> -<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> +<Project DefaultTargets="Build" ToolsVersion="%(versionstr)s" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">  """  V10DSPProjectConfiguration = """\ @@ -1067,6 +1120,7 @@ V10DSPGlobals = """\  \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>  """ @@ -1080,7 +1134,7 @@ V10DSPPropertyGroupCondition = """\  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\t<Import Project="$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />  \t</ImportGroup>  """ @@ -1104,9 +1158,9 @@ V15DSPHeader = """\  class _GenerateV10DSP(_DSPGenerator, _GenerateV10User):      """Generates a Project file for MSVS 2010""" -    def __init__(self, dspfile, header, source, env): +    def __init__(self, dspfile, source, env):          _DSPGenerator.__init__(self, dspfile, source, env) -        self.dspheader = header +        self.dspheader = V10DSPHeader          self.dspconfiguration = V10DSPProjectConfiguration          self.dspglobals = V10DSPGlobals @@ -1115,6 +1169,7 @@ class _GenerateV10DSP(_DSPGenerator, _GenerateV10User):      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', '') @@ -1159,7 +1214,7 @@ class _GenerateV10DSP(_DSPGenerator, _GenerateV10User):          name = self.name          confkeys = sorted(self.configs.keys()) -        self.file.write('\t<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />\n') +        self.file.write('\t<Import Project="$(VCTargetsPath)\\Microsoft.Cpp.Default.props" />\n')          toolset = ''          if 'MSVC_VERSION' in self.env: @@ -1170,7 +1225,7 @@ class _GenerateV10DSP(_DSPGenerator, _GenerateV10User):              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<Import Project="$(VCTargetsPath)\\Microsoft.Cpp.props" />\n')          self.file.write('\t<ImportGroup Label="ExtensionSettings">\n')          self.file.write('\t</ImportGroup>\n') @@ -1190,6 +1245,8 @@ class _GenerateV10DSP(_DSPGenerator, _GenerateV10User):              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: @@ -1207,9 +1264,8 @@ class _GenerateV10DSP(_DSPGenerator, _GenerateV10User):              # 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(self.env.get('CPPDEFINES', [])))) -            includepath_Dirs = processIncludes(self.env.get('CPPPATH', []), self.env, None, None) -            includepath = xmlify(';'.join([str(x) for x in includepath_Dirs])) +            preprocdefs = xmlify(';'.join(processDefines(cppdefines))) +            includepath = xmlify(';'.join(processIncludes(cpppaths, self.env, None, None)))              if not env_has_buildtarget:                  del self.env['MSVSBUILDTARGET'] @@ -1226,14 +1282,15 @@ class _GenerateV10DSP(_DSPGenerator, _GenerateV10User):              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="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">\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' +        self.file.write('\t<Import Project="$(VCTargetsPath)\\Microsoft.Cpp.targets" />\n'                          '\t<ImportGroup Label="ExtensionTargets">\n'                          '\t</ImportGroup>\n'                          '</Project>\n') @@ -1241,10 +1298,10 @@ class _GenerateV10DSP(_DSPGenerator, _GenerateV10User):          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.encodestring(pdata).decode() +            pdata = base64.b64encode(pdata).decode()              self.file.write('<!-- SCons Data:\n' + pdata + '\n')              pdata = pickle.dumps(self.sources,PICKLE_PROTOCOL) -            pdata = base64.encodestring(pdata).decode() +            pdata = base64.b64encode(pdata).decode()              self.file.write(pdata + '-->\n')      def printFilters(self, hierarchy, name): @@ -1451,13 +1508,16 @@ class _GenerateV7DSW(_DSWGenerator):          self.platforms = []          for key in list(self.configs.keys()):              platform = self.configs[key].platform -            if not platform in self.platforms: +            if platform not in self.platforms:                  self.platforms.append(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 @@ -1491,6 +1551,7 @@ class _GenerateV7DSW(_DSWGenerator):          while line:              line = dswfile.readline()              datas = datas + line +        dswfile.close()          # OK, we've found our little pickled cache of data.          try: @@ -1506,7 +1567,11 @@ class _GenerateV7DSW(_DSWGenerator):      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.0: +        if 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') @@ -1617,7 +1682,7 @@ class _GenerateV7DSW(_DSWGenerator):          self.file.write('EndGlobal\n')          if self.nokeep == 0:              pdata = pickle.dumps(self.configs,PICKLE_PROTOCOL) -            pdata = base64.encodestring(pdata).decode() +            pdata = base64.b64encode(pdata).decode()              self.file.write(pdata)              self.file.write('\n') @@ -1686,11 +1751,8 @@ def GenerateDSP(dspfile, source, env):      version_num = 6.0      if 'MSVS_VERSION' in env:          version_num, suite = msvs_parse_version(env['MSVS_VERSION']) -    if version_num > 14.0: -        g = _GenerateV10DSP(dspfile, V15DSPHeader, source, env) -        g.Build() -    elif version_num >= 10.0: -        g = _GenerateV10DSP(dspfile, V10DSPHeader, source, env) +    if version_num >= 10.0: +        g = _GenerateV10DSP(dspfile, source, env)          g.Build()      elif version_num >= 7.0:          g = _GenerateV7DSP(dspfile, source, env) @@ -1730,7 +1792,7 @@ def GenerateProject(target, source, env):      dspfile = builddspfile.srcnode()      # this detects whether or not we're using a VariantDir -    if not dspfile is builddspfile: +    if dspfile is not builddspfile:          try:              bdsp = open(str(builddspfile), "w+")          except IOError as detail: @@ -1738,6 +1800,7 @@ def GenerateProject(target, source, env):              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) @@ -1745,7 +1808,7 @@ def GenerateProject(target, source, env):          builddswfile = target[1]          dswfile = builddswfile.srcnode() -        if not dswfile is builddswfile: +        if dswfile is not builddswfile:              try:                  bdsw = open(str(builddswfile), "w+") @@ -1754,6 +1817,7 @@ def GenerateProject(target, source, env):                  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) @@ -1781,11 +1845,10 @@ def projectEmitter(target, source, env):          # Project file depends on CPPDEFINES and CPPPATH          preprocdefs = xmlify(';'.join(processDefines(env.get('CPPDEFINES', [])))) -        includepath_Dirs = processIncludes(env.get('CPPPATH', []), env, None, None) -        includepath = xmlify(';'.join([str(x) for x in includepath_Dirs])) +        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'] != None: +        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']): @@ -1799,7 +1862,7 @@ def projectEmitter(target, source, env):                  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'] != 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']): @@ -1962,8 +2025,14 @@ def generate(env):              default_MSVS_SConscript = env.File('SConstruct')          env['MSVSSCONSCRIPT'] = default_MSVS_SConscript -    env['MSVSSCONS'] = '"%s" -c "%s"' % (python_executable, getExecScriptMain(env)) -    env['MSVSSCONSFLAGS'] = '-C "${MSVSSCONSCRIPT.dir.get_abspath()}" -f ${MSVSSCONSCRIPT.name}' +    # 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"'  | 
