Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MSVS test fixes and minor changes to msvs tool #4610

Merged
merged 16 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 76 additions & 24 deletions SCons/Tool/msvs.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,24 @@ 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:
* MSVSProject projectguid argument
* MSVS_PROJECT_GUID in environment
* Generated from the project file name (dspfile)

Returns (str)
"""
project_guid = env.get('projectguid')
if not project_guid:
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+)(.*)')


Expand Down Expand Up @@ -428,6 +446,9 @@ class _DSPGenerator:
'misc']

def __init__(self, dspfile, source, env) -> None:
dspnode = env.File(dspfile)
self.project_guid = _projectGUID(env, dspfile)
dspnode.Tag('project_guid', self.project_guid)
self.dspfile = str(dspfile)
try:
get_abspath = dspfile.get_abspath
Expand Down Expand Up @@ -912,8 +933,9 @@ def __init__(self, dspfile, source, env) -> None:

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', '')
Expand All @@ -923,9 +945,6 @@ def PrintHeader(self) -> None:
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 != '':
Expand Down Expand Up @@ -1209,8 +1228,8 @@ def PrintHeader(self) -> None:
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', '')
Expand All @@ -1219,8 +1238,6 @@ def PrintHeader(self) -> None:
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 != '':
Expand Down Expand Up @@ -1465,22 +1482,35 @@ def Build(self):

_GenerateV10User.Build(self)

def _projectDSPNodes(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.")
sln_suffix = env.subst('$MSVSSOLUTIONSUFFIX')
dspnodes = []
for p in projects:
node = env.File(p)
if str(node).endswith(sln_suffix):
continue
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:
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))
dspnodes = _projectDSPNodes(env)
self.dsp_srcnodes = [dspnode.srcnode() for dspnode in dspnodes]

if 'name' in self.env:
self.name = self.env['name']
Expand Down Expand Up @@ -1554,7 +1584,9 @@ def AddConfig(self, variant, dswfile=dswfile) -> None:
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':
Expand All @@ -1565,8 +1597,10 @@ def GenerateProjectFilesInfo(self) -> None:
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,
Expand Down Expand Up @@ -1615,7 +1649,7 @@ def PrintSolution(self) -> None:
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:
Expand All @@ -1632,7 +1666,7 @@ def PrintSolution(self) -> None:
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']))
Expand All @@ -1645,7 +1679,7 @@ def PrintSolution(self) -> None:

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')
Expand Down Expand Up @@ -1777,7 +1811,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):
Expand Down Expand Up @@ -1867,7 +1901,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()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should ever be writing into the src directory of a variant (or into a Repository())
What's the reason for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some previous discussion here: #4612 (comment) and a number of successive posts.

The generated solution and project files have source code references which is why I'm guessing they were originally written the way the are. Opening the visual studio solution in the source directory will still build in the variant directory.

If the project artifacts were generated in the variant build directory, then all source files would need relative paths back to the source directory. There are a number of user specified locations that may be affected as well.

The project files have been written this way going way back (possibly to version 1). While the project file were written with a placeholder in the variant dir, the solution file was generated in the variant dir rather than source folder.

Getting the relative links to the source file locations is not easy. I was working on attempting the generate the solution/project files in their own folder (i.e., not the source folder and not the variant dir) but didn't know how some of the user specified paths/files should work. For example, is a file specified with no path because the generated environment locates it?

As one would have one project file may use multiple variant directories (i.e., one build folder for debug and one build folder for release). There is only one solution file and one project file (with both msvs variants). If there were two build folders the solution and project files would have to written to multiple folders (i.e., multiple copies).


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."""
Expand Down Expand Up @@ -1961,7 +2010,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

Expand Down Expand Up @@ -2028,6 +2077,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)
Expand Down
3 changes: 3 additions & 0 deletions SCons/Tool/msvsTests.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,9 @@ def get(self, name, value=None):
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:]
Expand Down
11 changes: 3 additions & 8 deletions test/MSVS/common-prefix.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
msg = "Skipping Visual Studio test on non-Windows platform '%s'\n" % sys.platform
test.skip_test(msg)

project_guid = TestSConsMSVS.PROJECT_GUID

vcproj_template = """\
<?xml version="1.0" encoding="Windows-1252"?>
<VisualStudioProject
Expand Down Expand Up @@ -83,23 +85,20 @@
</VisualStudioProject>
"""



SConscript_contents = """\
env=Environment(tools=['msvs'], MSVS_VERSION = '8.0')

testsrc = %(testsrc)s

env.MSVSProject(target = 'Test.vcproj',
projectguid = '%(project_guid)s',
slnguid = '{SLNGUID}',
srcs = testsrc,
buildtarget = 'Test.exe',
variant = 'Release',
auto_build_solution = 0)
"""



test.subdir('work1')

testsrc = repr([
Expand Down Expand Up @@ -142,8 +141,6 @@
# don't compare the pickled data
assert vcproj[:len(expect)] == expect, test.diff_substr(expect, vcproj)



test.subdir('work2')

testsrc = repr([
Expand All @@ -170,8 +167,6 @@
# don't compare the pickled data
assert vcproj[:len(expect)] == expect, test.diff_substr(expect, vcproj)



test.pass_test()

# Local Variables:
Expand Down
11 changes: 4 additions & 7 deletions test/MSVS/runfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
msg = "Skipping Visual Studio test on non-Windows platform '%s'\n" % sys.platform
test.skip_test(msg)

sconscript_dict = {'PROJECT_GUID': TestSConsMSVS.PROJECT_GUID}

expected_vcprojfile = """\
<?xml version="1.0" encoding="Windows-1252"?>
<VisualStudioProject
Expand Down Expand Up @@ -90,12 +92,11 @@
</VisualStudioProject>
"""



SConscript_contents = """\
env=Environment(tools=['msvs'], MSVS_VERSION = '8.0')

env.MSVSProject(target = 'Test.vcproj',
projectguid = '%(PROJECT_GUID)s',
slnguid = '{SLNGUID}',
srcs = ['test.cpp'],
buildtarget = 'Test.exe',
Expand All @@ -104,11 +105,9 @@
auto_build_solution = 0)
"""



test.subdir('work1')

test.write(['work1', 'SConstruct'], SConscript_contents)
test.write(['work1', 'SConstruct'], SConscript_contents % sconscript_dict)

test.run(chdir='work1', arguments="Test.vcproj")

Expand All @@ -118,8 +117,6 @@
# don't compare the pickled data
assert vcproj[:len(expect)] == expect, test.diff_substr(expect, vcproj)



test.pass_test()

# Local Variables:
Expand Down
14 changes: 3 additions & 11 deletions test/MSVS/vs-6.0-clean.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,12 @@
test = TestSConsMSVS.TestSConsMSVS()
host_arch = test.get_vs_host_arch()



# Make the test infrastructure think we have this version of MSVS installed.
test._msvs_versions = ['6.0']



expected_dspfile = TestSConsMSVS.expected_dspfile_6_0
expected_dswfile = TestSConsMSVS.expected_dswfile_6_0



test.write('SConstruct', """\
env=Environment(platform='win32', tools=['msvs'],
MSVS_VERSION='6.0',HOST_ARCH='%(HOST_ARCH)s')
Expand Down Expand Up @@ -95,16 +89,14 @@
test.must_exist(test.workpath('Test.dsp'))
test.must_exist(test.workpath('Test.dsw'))

test.run(arguments='-c Test.dsw')

test.must_exist(test.workpath('Test.dsp'))
test.must_not_exist(test.workpath('Test.dsw'))

test.run(arguments='-c Test.dsp')

test.must_not_exist(test.workpath('Test.dsp'))
test.must_exist(test.workpath('Test.dsw'))

test.run(arguments='-c Test.dsw')

test.must_not_exist(test.workpath('Test.dsw'))

test.pass_test()

Expand Down
Loading
Loading