From 7bc5b0f51a17b8ea1e49cfe917a98610791d67aa Mon Sep 17 00:00:00 2001 From: Steven Knight Date: Sun, 12 Feb 2006 15:34:44 +0000 Subject: Fix over-zealous common prefix matching when generating Visual Studio project files. (David J. Van Maren) Avoid overwriting available MSVS versions that are already set. (Thad Ward) Add a runfile parameter to Project creation. (Matthias) --- src/CHANGES.txt | 15 +++++ src/engine/SCons/Tool/msvs.py | 82 ++++++++++++++++-------- src/engine/SCons/Tool/msvs.xml | 18 +++++- test/MSVS/common-prefix.py | 138 +++++++++++++++++++++++++++++++++++++++++ test/MSVS/runfile.py | 130 ++++++++++++++++++++++++++++++++++++++ test/MSVS/vs-7.0-files.py | 11 ++-- test/MSVS/vs-7.1-files.py | 11 ++-- test/MSVS/vs-8.0-files.py | 11 ++-- 8 files changed, 377 insertions(+), 39 deletions(-) create mode 100644 test/MSVS/common-prefix.py create mode 100644 test/MSVS/runfile.py diff --git a/src/CHANGES.txt b/src/CHANGES.txt index 1407e3f..5a6935e 100644 --- a/src/CHANGES.txt +++ b/src/CHANGES.txt @@ -25,6 +25,12 @@ RELEASE 0.97 - XXX - Support generating more than one project file for a Microsoft Visual Studio solution file. + - Add support for a support "runfile" parameter to Microsoft + Visual Studio project file creation. + + - Put the project GUID, not the solution GUID, in the right spot + in the solution file. + From Erling Andersen: - Fix interpretation of Node.FS objects wrapped in Proxy instances, @@ -226,6 +232,15 @@ RELEASE 0.97 - XXX external environment, or settable internally) to put a shortened SCons execution line in the Visual Studio project file. + From David J. Van Maren: + + - Only filter common prefixes from source files names in Visual Studio + project files if the prefix is a complete (sub)directory name. + + From Thad Ward: + + - If $MSVSVERSIONS is already set, don't overwrite it with + information from the registry. diff --git a/src/engine/SCons/Tool/msvs.py b/src/engine/SCons/Tool/msvs.py index 6747d54..074733a 100644 --- a/src/engine/SCons/Tool/msvs.py +++ b/src/engine/SCons/Tool/msvs.py @@ -72,6 +72,8 @@ def xmlify(s): s = string.replace(s, '"', """) return s +external_makefile_guid = '{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}' + def _generateGUID(slnfile, name): """This generates a dummy GUID for the sln file to use. It is based on the MD5 signatures of the sln filename plus the name of @@ -228,6 +230,28 @@ class _DSPGenerator: for v in variants: outdir.append(s) + if not env.has_key('runfile') or env['runfile'] == 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'] cmdargs = env.get('cmdargs', '') @@ -238,6 +262,7 @@ class _DSPGenerator: 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', @@ -272,11 +297,12 @@ class _DSPGenerator: for n in sourcenames: self.sources[n].sort(lambda a, b: cmp(a.lower(), b.lower())) - def AddConfig(variant, buildtarget, outdir, cmdargs): + def AddConfig(variant, buildtarget, outdir, runfile, cmdargs): config = Config() config.buildtarget = buildtarget config.outdir = outdir config.cmdargs = cmdargs + config.runfile = runfile match = re.match('(.*)\|(.*)', variant) if match: @@ -290,7 +316,7 @@ class _DSPGenerator: print "Adding '" + self.name + ' - ' + config.variant + '|' + config.platform + "' to '" + str(dspfile) + "'" for i in range(len(variants)): - AddConfig(variants[i], buildtarget[i], outdir[i],cmdargs) + AddConfig(variants[i], buildtarget[i], outdir[i], runfile[i], cmdargs) self.platforms = [] for key in self.configs.keys(): @@ -521,7 +547,7 @@ V7DSPConfiguration = """\ \t\t\t\tBuildCommandLine="%(buildcmd)s" \t\t\t\tCleanCommandLine="%(cleancmd)s" \t\t\t\tRebuildCommandLine="%(rebuildcmd)s" -\t\t\t\tOutput="%(buildtarget)s"/> +\t\t\t\tOutput="%(runfile)s"/> \t\t """ @@ -548,7 +574,7 @@ V8DSPConfiguration = """\ \t\t\t\tBuildCommandLine="%(buildcmd)s" \t\t\t\tReBuildCommandLine="%(rebuildcmd)s" \t\t\t\tCleanCommandLine="%(cleancmd)s" -\t\t\t\tOutput="%(buildtarget)s" +\t\t\t\tOutput="%(runfile)s" \t\t\t\tPreprocessorDefinitions="" \t\t\t\tIncludeSearchPath="" \t\t\t\tForcedIncludes="" @@ -624,6 +650,7 @@ class _GenerateV7DSP(_DSPGenerator): 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 env_has_buildtarget = self.env.has_key('MSVSBUILDTARGET') @@ -682,7 +709,7 @@ class _GenerateV7DSP(_DSPGenerator): '\t\t\tFilter="%s">\n' % (kind, categories[kind])) - def printSources(hierarchy): + def printSources(hierarchy, commonprefix): sorteditems = hierarchy.items() sorteditems.sort(lambda a, b: cmp(a[0].lower(), b[0].lower())) @@ -692,7 +719,7 @@ class _GenerateV7DSP(_DSPGenerator): self.file.write('\t\t\t\n' % (key)) - printSources(value) + printSources(value, commonprefix) self.file.write('\t\t\t\n') for key, value in sorteditems: @@ -710,13 +737,14 @@ class _GenerateV7DSP(_DSPGenerator): # First remove any common prefix commonprefix = None if len(sources) > 1: - commonprefix = os.path.commonprefix(sources) - prefixlen = len(commonprefix) - if prefixlen: - sources = map(lambda s, p=prefixlen: s[p:], sources) + s = map(os.path.normpath, sources) + cp = os.path.commonprefix(s) + if cp and s[0][len(cp)] == os.path.sep: + sources = map(lambda s, l=len(cp): s[l:], sources) + commonprefix = cp hierarchy = makeHierarchy(sources) - printSources(hierarchy) + printSources(hierarchy, commonprefix=commonprefix) if len(cats)>1: self.file.write('\t\t\n') @@ -810,6 +838,7 @@ class _DSWGenerator: 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 @@ -913,9 +942,9 @@ class _GenerateV7DSW(_DSWGenerator): base, suffix = SCons.Util.splitext(name) if suffix == '.vcproj': name = base - # the next line has the GUID for an external makefile project. - self.file.write('Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "%s", "%s", "%s"\n' - % ( name, p, self.slnguid ) ) + guid = _generateGUID(p, '') + self.file.write('Project("%s") = "%s", "%s", "%s"\n' + % ( external_makefile_guid, name, p, guid ) ) if self.version_num >= 7.1 and self.version_num < 8.0: self.file.write('\tProjectSection(ProjectDependencies) = postProject\n' '\tEndProjectSection\n') @@ -1102,21 +1131,24 @@ def get_default_visualstudio_version(env): version = '6.0' versions = [version] + if not env.has_key('MSVS') or not SCons.Util.is_Dict(env['MSVS']): - env['MSVS'] = {} + env['MSVS'] = {} - if env.has_key('MSVS_VERSION'): - version = env['MSVS_VERSION'] - versions = [version] - else: if SCons.Util.can_read_reg: - versions = get_visualstudio_versions() - if versions: - version = versions[0] #use highest version by default + v = get_visualstudio_versions() + if v: + versions = v + if env.has_key('MSVS_VERSION'): + version = env['MSVS_VERSION'] + else: + version = versions[0] #use highest version by default - env['MSVS_VERSION'] = version - env['MSVS']['VERSIONS'] = versions - env['MSVS']['VERSION'] = version + env['MSVS_VERSION'] = version + env['MSVS']['VERSIONS'] = versions + env['MSVS']['VERSION'] = version + else: + version = env['MSVS']['VERSION'] return version diff --git a/src/engine/SCons/Tool/msvs.xml b/src/engine/SCons/Tool/msvs.xml index babc20f..56180b0 100644 --- a/src/engine/SCons/Tool/msvs.xml +++ b/src/engine/SCons/Tool/msvs.xml @@ -61,8 +61,11 @@ NOT as SCons File Nodes. This is because they represent file names to be added to the project file, not the source files used to build the project file. -In addition to the above lists of values (which are all optional, -although not specifying any of them results in an empty project file), +The above filename lists are all optional, +although at least one must be specified +for the resulting project file to be non-empty. + +In addition to the above lists of values, the following values may be specified: target: @@ -108,6 +111,17 @@ entries must match the number of variant entries. +runfile: +The name of the file that Visual Studio 7 and later +will run and debug. +This appears as the value of the +Output +field in the resutling Visual Studio project file. +If this is not specified, +the default is the same as the specified +buildtarget +value. + Example usage: diff --git a/test/MSVS/common-prefix.py b/test/MSVS/common-prefix.py new file mode 100644 index 0000000..6f508c9 --- /dev/null +++ b/test/MSVS/common-prefix.py @@ -0,0 +1,138 @@ + +#!/usr/bin/env python +# +# __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__" + +""" +Test that we can generate Visual Studio 8.0 project (.vcproj) and +solution (.sln) files that look correct. +""" + +import os +import os.path +import sys + +import TestCmd +import TestSCons + +test = TestSCons.TestSCons() + +if sys.platform != 'win32': + msg = "Skipping Visual Studio test on non-Windows platform '%s'\n" % sys.platform + test.skip_test(msg) + +expected_vcprojfile = """\ + + +\t +\t\t +\t +\t +\t +\t +\t\t +\t\t\t +\t\t +\t +\t +\t +\t +\t\t\t +\t\t\t +\t\t\t +\t\t\t +\t\t\t +\t\t\t +\t\t +\t\t +\t +\t +\t + +""" + + + +SConscript_contents = """\ +env=Environment(MSVS_VERSION = '8.0') + +testsrc = ['subdir/test1.cpp', r'subdir\\test2.cpp'] + +env.MSVSProject(target = 'Test.vcproj', + slnguid = '{SLNGUID}', + srcs = testsrc, + buildtarget = 'Test.exe', + variant = 'Release', + auto_build_solution = 0) +""" + + + +test.subdir('work1') + +test.write(['work1', 'SConstruct'], SConscript_contents) + +test.run(chdir='work1', arguments="Test.vcproj") + +test.must_exist(test.workpath('work1', 'Test.vcproj')) +vcproj = test.read(['work1', 'Test.vcproj'], 'r') +expect = test.msvs_substitute(expected_vcprojfile, '8.0', 'work1', 'SConstruct') +# don't compare the pickled data +assert vcproj[:len(expect)] == expect, test.diff_substr(expect, vcproj) + + + +test.pass_test() diff --git a/test/MSVS/runfile.py b/test/MSVS/runfile.py new file mode 100644 index 0000000..581de64 --- /dev/null +++ b/test/MSVS/runfile.py @@ -0,0 +1,130 @@ + +#!/usr/bin/env python +# +# __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__" + +""" +Test that we can generate Visual Studio 8.0 project (.vcproj) and +solution (.sln) files that look correct. +""" + +import os +import os.path +import sys + +import TestCmd +import TestSCons + +test = TestSCons.TestSCons() + +if sys.platform != 'win32': + msg = "Skipping Visual Studio test on non-Windows platform '%s'\n" % sys.platform + test.skip_test(msg) + +expected_vcprojfile = """\ + + +\t +\t\t +\t +\t +\t +\t +\t\t +\t\t\t +\t\t +\t +\t +\t +\t +\t\t\t +\t\t\t +\t\t +\t\t +\t +\t +\t + +""" + + + +SConscript_contents = """\ +env=Environment(MSVS_VERSION = '8.0') + +env.MSVSProject(target = 'Test.vcproj', + slnguid = '{SLNGUID}', + srcs = ['test.cpp'], + buildtarget = 'Test.exe', + runfile = 'runfile.exe', + variant = 'Release', + auto_build_solution = 0) +""" + + + +test.subdir('work1') + +test.write(['work1', 'SConstruct'], SConscript_contents) + +test.run(chdir='work1', arguments="Test.vcproj") + +test.must_exist(test.workpath('work1', 'Test.vcproj')) +vcproj = test.read(['work1', 'Test.vcproj'], 'r') +expect = test.msvs_substitute(expected_vcprojfile, '8.0', 'work1', 'SConstruct') +# don't compare the pickled data +assert vcproj[:len(expect)] == expect, test.diff_substr(expect, vcproj) + + + +test.pass_test() diff --git a/test/MSVS/vs-7.0-files.py b/test/MSVS/vs-7.0-files.py index e682607..8c82d1a 100644 --- a/test/MSVS/vs-7.0-files.py +++ b/test/MSVS/vs-7.0-files.py @@ -44,7 +44,7 @@ if sys.platform != 'win32': expected_slnfile = """\ Microsoft Visual Studio Solution File, Format Version 7.00 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Test", "Test.vcproj", "{SLNGUID}" +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Test", "Test.vcproj", "{E5466E26-0003-F18B-8F8A-BCD76C86388D}" EndProject Global \tGlobalSection(SolutionConfiguration) = preSolution @@ -126,7 +126,10 @@ expected_vcprojfile = """\ \t\t\tName="Source Files" \t\t\tFilter="cpp;c;cxx;l;y;def;odl;idl;hpj;bat"> \t\t\t +\t\t\t\tRelativePath="test1.cpp"> +\t\t\t +\t\t\t \t\t\t \t\t \t\t \t\t\t +\t\t\t\tRelativePath="test1.cpp"> +\t\t\t +\t\t\t \t\t\t \t\t \t\t \t\t\t +\t\t\t\tRelativePath="test1.cpp"> +\t\t\t +\t\t\t \t\t\t \t\t \t\t