diff options
author | Joseph Brill <48932340+jcbrill@users.noreply.github.com> | 2024-11-11 00:49:24 (GMT) |
---|---|---|
committer | Joseph Brill <48932340+jcbrill@users.noreply.github.com> | 2024-11-11 00:49:24 (GMT) |
commit | 80ab029237edfd57d2bff094ec0335046859d3a4 (patch) | |
tree | eb825f0795ac03978d33dbeee40366051bdf9941 /SCons | |
parent | 382d1e293a1ddf268d62e993e4f7ae3c75501633 (diff) | |
parent | 1215e13dc1398ea9358e4b50fc8f1b5086b17316 (diff) | |
download | SCons-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.py | 118 | ||||
-rw-r--r-- | SCons/Tool/msvs.xml | 68 | ||||
-rw-r--r-- | SCons/Tool/msvsTests.py | 3 |
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:] |