summaryrefslogtreecommitdiffstats
path: root/SCons
diff options
context:
space:
mode:
authorJoseph Brill <48932340+jcbrill@users.noreply.github.com>2024-11-11 00:49:24 (GMT)
committerJoseph Brill <48932340+jcbrill@users.noreply.github.com>2024-11-11 00:49:24 (GMT)
commit80ab029237edfd57d2bff094ec0335046859d3a4 (patch)
treeeb825f0795ac03978d33dbeee40366051bdf9941 /SCons
parent382d1e293a1ddf268d62e993e4f7ae3c75501633 (diff)
parent1215e13dc1398ea9358e4b50fc8f1b5086b17316 (diff)
downloadSCons-80ab029237edfd57d2bff094ec0335046859d3a4.zip
SCons-80ab029237edfd57d2bff094ec0335046859d3a4.tar.gz
SCons-80ab029237edfd57d2bff094ec0335046859d3a4.tar.bz2
Merge branch 'master' into jbrill-gh4623-int
Diffstat (limited to 'SCons')
-rw-r--r--SCons/Tool/msvs.py118
-rw-r--r--SCons/Tool/msvs.xml68
-rw-r--r--SCons/Tool/msvsTests.py3
3 files changed, 163 insertions, 26 deletions
diff --git a/SCons/Tool/msvs.py b/SCons/Tool/msvs.py
index 1297448..b327653 100644
--- a/SCons/Tool/msvs.py
+++ b/SCons/Tool/msvs.py
@@ -124,6 +124,21 @@ def _generateGUID(slnfile, name, namespace=external_makefile_guid):
return '{' + str(solution).upper() + '}'
+def _projectGUID(env, dspfile):
+ """Generates a GUID for the project file to use.
+
+ In order of preference:
+ * MSVS_PROJECT_GUID in environment
+ * Generated from the project file name (dspfile)
+
+ Returns (str)
+ """
+ project_guid = env.get('MSVS_PROJECT_GUID')
+ if not project_guid:
+ project_guid = _generateGUID(dspfile, '')
+ return project_guid
+
+
version_re = re.compile(r'(\d+\.\d+)(.*)')
@@ -436,6 +451,10 @@ class _DSPGenerator:
else:
self.dspabs = get_abspath()
+ self.project_guid = _projectGUID(env, self.dspfile)
+ dspnode = env.File(self.dspfile)
+ dspnode.Tag('project_guid', self.project_guid)
+
if 'variant' not in env:
raise SCons.Errors.InternalError("You must specify a 'variant' argument (i.e. 'Debug' or " +\
"'Release') to create an MSVSProject.")
@@ -571,7 +590,7 @@ class _DSPGenerator:
self.configs = {}
self.nokeep = 0
- if 'nokeep' in env and env['variant'] != 0:
+ if 'nokeep' in env and env['nokeep'] != 0:
self.nokeep = 1
if self.nokeep == 0 and os.path.exists(self.dspabs):
@@ -912,8 +931,9 @@ class _GenerateV7DSP(_DSPGenerator, _GenerateV7User):
def PrintHeader(self) -> None:
env = self.env
- versionstr = self.versionstr
name = self.name
+ versionstr = self.versionstr
+ project_guid = self.project_guid
encoding = self.env.subst('$MSVSENCODING')
scc_provider = env.get('MSVS_SCC_PROVIDER', '')
scc_project_name = env.get('MSVS_SCC_PROJECT_NAME', '')
@@ -923,9 +943,6 @@ class _GenerateV7DSP(_DSPGenerator, _GenerateV7User):
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 != '':
@@ -1209,8 +1226,8 @@ class _GenerateV10DSP(_DSPGenerator, _GenerateV10User):
env = self.env
name = self.name
versionstr = self.versionstr
+ project_guid = self.project_guid
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', '')
@@ -1219,8 +1236,6 @@ class _GenerateV10DSP(_DSPGenerator, _GenerateV10User):
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 != '':
@@ -1465,6 +1480,42 @@ class _GenerateV10DSP(_DSPGenerator, _GenerateV10User):
_GenerateV10User.Build(self)
+def _projectDSPNodes(env):
+ auto_filter_projects = env.get('auto_filter_projects', None)
+ 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.")
+ sln_suffix = os.path.normcase(env.subst('$MSVSSOLUTIONSUFFIX'))
+ dspnodes = []
+ for p in projects:
+ node = env.File(p)
+ if os.path.normcase(str(node)).endswith(sln_suffix):
+ # solution node detected (possible issues when opening generated solution file with VS IDE)
+ if auto_filter_projects is None:
+ nodestr = str(node)
+ errmsg = (
+ f"An msvs solution file was detected in the MSVSSolution 'projects' argument: {nodestr!r}.\n"
+ " Add MSVSSolution argument 'auto_filter_projects=True' to suppress project solution nodes.\n"
+ " Add MSVSSolution argument 'auto_filter_projects=False' to keep project solution nodes.\n"
+ " Refer to the MSVSSolution documentation for more information."
+ )
+ raise SCons.Errors.UserError(errmsg)
+ elif auto_filter_projects:
+ # skip solution node
+ continue
+ else:
+ # keep solution node
+ pass
+ dspnodes.append(node)
+ if len(dspnodes) < 1:
+ raise SCons.Errors.UserError("You must specify at least one project node to create an MSVSSolution.")
+ return dspnodes
+
class _DSWGenerator:
""" Base class for DSW generators """
def __init__(self, dswfile, source, env) -> None:
@@ -1472,15 +1523,8 @@ class _DSWGenerator:
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))
+ dspnodes = _projectDSPNodes(env)
+ self.dsp_srcnodes = [dspnode.srcnode() for dspnode in dspnodes]
if 'name' in self.env:
self.name = self.env['name']
@@ -1519,7 +1563,7 @@ class _GenerateV7DSW(_DSWGenerator):
self.configs = {}
self.nokeep = 0
- if 'nokeep' in env and env['variant'] != 0:
+ if 'nokeep' in env and env['nokeep'] != 0:
self.nokeep = 1
if self.nokeep == 0 and os.path.exists(self.dswfile):
@@ -1554,7 +1598,9 @@ class _GenerateV7DSW(_DSWGenerator):
if not (p.platform in seen or seen.add(p.platform))]
def GenerateProjectFilesInfo(self) -> None:
- for dspfile in self.dspfiles:
+ for dspnode in self.dsp_srcnodes:
+ project_guid = dspnode.GetTag('project_guid')
+ dspfile = str(dspnode)
dsp_folder_path, name = os.path.split(dspfile)
dsp_folder_path = os.path.abspath(dsp_folder_path)
if SCons.Util.splitext(name)[1] == '.filters':
@@ -1565,8 +1611,10 @@ class _GenerateV7DSW(_DSWGenerator):
dsp_relative_file_path = name
else:
dsp_relative_file_path = os.path.join(dsp_relative_folder_path, name)
+ if not project_guid:
+ project_guid = _generateGUID(dspfile, '')
dspfile_info = {'NAME': name,
- 'GUID': _generateGUID(dspfile, ''),
+ 'GUID': project_guid,
'FOLDER_PATH': dsp_folder_path,
'FILE_PATH': dspfile,
'SLN_RELATIVE_FOLDER_PATH': dsp_relative_folder_path,
@@ -1615,7 +1663,7 @@ class _GenerateV7DSW(_DSWGenerator):
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:
+ 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:
@@ -1632,7 +1680,7 @@ class _GenerateV7DSW(_DSWGenerator):
for dspinfo in self.dspfiles_info:
name = dspinfo['NAME']
base, suffix = SCons.Util.splitext(name)
- if suffix == '.vcproj':
+ if suffix in ('.vcxproj', '.vcproj'):
name = base
self.file.write('Project("%s") = "%s", "%s", "%s"\n'
% (external_makefile_guid, name, dspinfo['SLN_RELATIVE_FILE_PATH'], dspinfo['GUID']))
@@ -1645,7 +1693,7 @@ class _GenerateV7DSW(_DSWGenerator):
env = self.env
if 'MSVS_SCC_PROVIDER' in env:
- scc_number_of_projects = len(self.dspfiles) + 1
+ scc_number_of_projects = len(self.dsp_srcnodes) + 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')
@@ -1777,7 +1825,7 @@ class _GenerateV6DSW(_DSWGenerator):
def PrintWorkspace(self) -> None:
""" writes a DSW file """
name = self.name
- dspfile = os.path.relpath(self.dspfiles[0], self.dsw_folder_path)
+ dspfile = os.path.relpath(str(self.dsp_srcnodes[0]), self.dsw_folder_path)
self.file.write(V6DSWHeader % locals())
def Build(self):
@@ -1867,7 +1915,22 @@ def GenerateProject(target, source, env):
GenerateDSW(dswfile, source, env)
def GenerateSolution(target, source, env) -> None:
- GenerateDSW(target[0], source, env)
+
+ builddswfile = target[0]
+ dswfile = builddswfile.srcnode()
+
+ if dswfile is not builddswfile:
+
+ try:
+ bdsw = open(str(builddswfile), "w+")
+ except OSError 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 projectEmitter(target, source, env):
"""Sets up the DSP dependencies."""
@@ -1961,7 +2024,7 @@ def projectEmitter(target, source, env):
sourcelist = source
if env.get('auto_build_solution', 1):
- env['projects'] = [env.File(t).srcnode() for t in targetlist]
+ env['projects'] = [env.File(t) for t in targetlist]
t, s = solutionEmitter(target, target, env)
targetlist = targetlist + t
@@ -2028,6 +2091,9 @@ def solutionEmitter(target, source, env):
source = source + ' "%s"' % str(target[0])
source = [SCons.Node.Python.Value(source)]
+ dsp_nodes = _projectDSPNodes(env)
+ source.extend(dsp_nodes)
+
return ([target[0]], source)
projectAction = SCons.Action.Action(GenerateProject, None)
diff --git a/SCons/Tool/msvs.xml b/SCons/Tool/msvs.xml
index cc4220c..ad3a756 100644
--- a/SCons/Tool/msvs.xml
+++ b/SCons/Tool/msvs.xml
@@ -540,6 +540,74 @@ env.MSVSProject(
</listitem>
</varlistentry>
</variablelist>
+ <para>
+ In addition to the mandatory arguments above, the following optional
+ values may be specified as keyword arguments:
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term><parameter>auto_filter_projects</parameter></term>
+ <listitem>
+ <para>
+ Under certain circumstances, solution file names or
+ solution file nodes may be present in the
+ <parameter>projects</parameter> argument list.
+ When solution file names or nodes are present in the
+ <parameter>projects</parameter> argument list, the generated
+ solution file may contain erroneous Project records resulting
+ in VS IDE error messages when opening the generated solution
+ file.
+ By default, an exception is raised when a solution file
+ name or solution file node is detected in the
+ <parameter>projects</parameter> argument list.
+ </para>
+ <para>
+ The accepted values for <parameter>auto_filter_projects</parameter>
+ are:
+ </para>
+ <variablelist>
+ <varlistentry>
+ <term><parameter>None</parameter></term>
+ <listitem>
+ <para>
+ An exception is raised when a solution file name or solution
+ file node is detected in the <parameter>projects</parameter>
+ argument list.
+ </para>
+ <para>
+ <parameter>None</parameter> is the default value.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><parameter>True or evaluates True</parameter></term>
+ <listitem>
+ <para>
+ Automatically remove solution file names and solution file
+ nodes from the <parameter>projects</parameter> argument list.
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry>
+ <term><parameter>False or evaluates False</parameter></term>
+ <listitem>
+ <para>
+ Leave the solution file names and solution file nodes
+ in the <parameter>projects</parameter> argument list.
+ An exception is not raised.
+ </para>
+ <para>
+ When opening the generated solution file with the VS IDE,
+ the VS IDE will likely report that there are erroneous
+ Project records that are not supported or that need to be
+ modified.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
+ </listitem>
+ </varlistentry>
+ </variablelist>
<para>Example Usage:</para>
<example_commands>
env.MSVSSolution(
diff --git a/SCons/Tool/msvsTests.py b/SCons/Tool/msvsTests.py
index 7893caf..dd423d4 100644
--- a/SCons/Tool/msvsTests.py
+++ b/SCons/Tool/msvsTests.py
@@ -420,6 +420,9 @@ class DummyEnv:
def Dir(self, name):
return self.fs.Dir(name)
+ def File(self, name):
+ return self.fs.File(name)
+
def subst(self, key):
if key[0] == '$':
key = key[1:]