Multiple-variants MSVS project doesn't work
Describe the bug Calling Evnrionment.MSVSProject multiple times with different variants does not create a MSVS project file with multiple build targets, as described in the manual
Required information
- Mailing list post with additional details
- Version 4.1.0 (also confirmed on 3.1.2)
- python.org version 3.8.5
- SCons installed with scons-local package from scons.org on Windows 10
Reproduction SConstruct
import os
from SCons.Script import *
env = Environment()
for variant in ["debug", "release"]:
env.MSVSProject(
target="hello" + env["MSVSPROJECTSUFFIX"],
srcs="hello.cpp",
buildtarget=os.path.join(variant, "hello.exe"),
variant=variant,
)
This gives the following output:
> scons
scons: Reading SConscript files ...
scons: warning: Two different environments were specified for target hello.vcxproj, but they appear to have the same action: GenerateProject(target, source, env)
File "SConstruct", line 7, in <module>
scons: *** Multiple ways to build the same target were specified for: hello.vcxproj (from ['prj_inputs:"python.exe" -c "from os.path import join; import sys; sys.path = [ join(sys.prefix, \'Lib\', \'site-packages\', \'scons-4.1.0\'), join(sys.prefix, \'scons-4.1.0\'), join(sys.prefix, \'Lib\', \'site-packages\', \'scons\'), join(sys.prefix, \'scons\') ] + sys.path; import SCons.Script; SCons.Script.main()" -C "." -f SConstructutf-8; ppdefs: incpath: "debug\\hello.exe" "debug" "hello.cpp "hello.vcxproj"'] and from ['prj_inputs:"python.exe" -c "from os.path import join; import sys; sys.path = [ join(sys.prefix, \'Lib\', \'site-packages\', \'scons-4.1.0\'), join(sys.prefix, \'scons-4.1.0\'), join(sys.prefix, \'Lib\', \'site-packages\', \'scons\'), join(sys.prefix, \'scons\') ] + sys.path; import SCons.Script; SCons.Script.main()" -C "." -f SConstructutf-8; ppdefs: incpath: "release\\hello.exe" "release" "hello.cpp "hello.vcxproj"'])
File "SConstruct", line 7, in <module>
Minimally the builder needs to have multi=true, and beyond that it's dumping the second (or any but the first variants specified.) (See mailing list thread referenced above)
@bdbaddog, @mwichmann: I believe the SCons behavior is correct in this specific case.
Reproduction SConstruct:
import os
from SCons.Script import *
env = Environment()
for variant in ["debug", "release"]:
env.MSVSProject(
target="hello" + env["MSVSPROJECTSUFFIX"],
srcs="hello.cpp",
buildtarget=os.path.join(variant, "hello.exe"),
variant=variant,
)
The target project file (e.g., Hello.vcxproj) is written into the source directory. Since the MSVSProject definition is inside a loop, multiple MSVSProjects are targeting the same physical file in source directory which explains the SCons error messages.
The documentation for MSVSProject indicates that a number of the keyword arguments can be strings or lists of strings but doesn't include a list usage example.
The generated project file is a container for build variants. One physical file can contain many build variants.
For multiple build variants in the same project file, the MSVSProject keyword arguments can be specified as lists with the length of each list being the same length as the list of variant names (i.e., parallel arrays of the same length).
When a list is specified for the variant argument, any scalar specifications for arguments that accept a scalar or list are promoted to a list of the same length as the variant argument (i.e., the scalar value is used for all variant definitions).
Conceptually, this:
import os
from SCons.Script import *
variants = ["debug", "release"]
env.MSVSProject(
target="hello" + env["MSVSPROJECTSUFFIX"],
variant=variants,
srcs="hello.cpp",
buildtarget=[
os.path.join(variant, "hello.exe")
for variant in variants
],
)
is equivalent to this:
import os
from SCons.Script import *
env.MSVSProject(
target="hello" + env["MSVSPROJECTSUFFIX"],
variant=["debug", "release]",
srcs=["hello.cpp", "hello.cpp"],
buildtarget=[r"debug\hello.exe", r"release\hello.exe"],
)
Documented MSVSProject arguments that can be a scalar or a list:
'variant': "variant must be a string or a list of strings"'srcs': "srcs must be a string or a list of strings"'incs': "incs must be a string or a list of strings"'localincs': "localincs must be a string or a list of strings"'resources': "resources must be a string or a list of strings"'misc': "misc must be a string or a list of strings"'cmdargs': string or list of strings'cppdefines': string or list of strings'cppflags': string or list of strings'cpppaths': string or list of strings'buildtarget': "buildtarget can be a string, a node, a list of strings or nodes, or None"'runfile': "runfile can be a string, a node, a list of strings or nodes, or None"
Undocumented MSVSProject arguments that can be a scalar or a list:
'outdir': "outdir can be a string, a node, a list of strings or nodes, or None"
Untested Alternate Method
An alternate method that might work (caveat emptor: not tested and likely erroneous) is to generate a project file with a unique name for each variant (i.e., each project file contains a single variant) and then manually specify a solution file containing the two projects:
import os
from SCons.Script import *
env = Environment()
variants = ["debug", "release"]
projects = [
env.MSVSProject(
target="hello_" + variant + env["MSVSPROJECTSUFFIX"],
srcs="hello.cpp",
buildtarget=os.path.join(variant, "hello.exe"),
variant=variant,
auto_build_solution=0,
)
for variant in ["debug", "release"]
]
env.MSVSSolution(
target="hello" + env["MSVSSOLUTIONSUFFIX"],
projects=projects,
variant=variants,
)
If you do the latter, I presume there are complications about files "shared" between projects (namely, all the sources)? There would have to be some plumbing, or each project thinks it "owns" a given file when it really doesn't. Or am I inventing problems that aren't real?
If you do the latter,
Which example is "latter" referring to:
- Conceptually this example
- Equivalent example, or
- Untested alternate example?
Or am I inventing problems that aren't real?
Probably not.
There are multiple concepts at play here: SCons variant_dir and/or MSVS Project build variants and wiring them together so that it makes sense and does the right thing.
I need to expand some of the local tests for multiple build variants and variant_dirs.
The parallel array requirement is a bit unfortunate.
If you do the latter,
Which example is "latter" referring to:
1. Conceptually this example 2. Equivalent example, or 3. Untested alternate example?
The third one - multiple .vsxproj files listed in one solution. Which is prefectly legal to do, I'm just worrying since they're not really distinct, they'd all be the same build with different details.
The third one - multiple .vsxproj files listed in one solution. Which is prefectly legal to do, I'm just worrying since they're not really distinct, they'd all be the same build with different details.
You may be right.
What is not shown in the example is how the build variant relates to the the SCons Program/SharedLibrary/Library configuration and possible output build locations. I have not tried all of the combinations yet so I'm not sure of the interactions yet.
I believe the buildtarget argument in the project would be the same as an alias for an SCons Program/SharedLibrary/StaticLibrary configuration and the runfile argument would be the produced artifact.
Modified sconscript with one variant ('Release') so it might not work directly. Need to work on expanding:
Import(['env'])
progname = 'hello.exe'
srcfile = 'hello.c'
srcfiles = [srcfile]
project = env.MSVSProject(
name = 'Hello',
target = Test' + env.subst('$MSVSPROJECTSUFFIX'),
#srcs = srcfile,
srcs = srcfiles,
buildtarget = 'myprog',
runfile = progname,
variant = 'Release',
auto_build_solution=0,
)
prog = env.Program("hello", srcfiles)
Alias('myprog', prog)
Return('project')
Modified SConstruct:
env = Environment(tools=['msvs', 'msvc', 'mslink'], MSVC_VERSION='14.3')
variant_dir = '#Release'
projects = SConscript('src/SConscript', exports=['env'], variant_dir=variant_dir, duplicate=False)
env.MSVSSolution(
name = 'Hello',
target = 'Test' + env.subst('$MSVSSOLUTIONSUFFIX'),
projects = projects,
variant = ['Release', 'Debug'],
)
It is entirely possible that I'm way off base here.
However, I'm pretty confident that the original issue is not technically a bug.
The third one - multiple .vsxproj files listed in one solution. Which is prefectly legal to do, I'm just worrying since they're not really distinct, they'd all be the same build with different details.
I think you're right. I couldn't get it work in a timely manner.
What follows is a simple example using a variant_dir for each build variant (Debug and Release). Everything done in SConstruct and vs solution/project files generated in root. Source file in src folder.
I'm a little rusty when it comes to cl/link flags.
The SConstruct could probably be made simpler.
Debug/Release variant example:
-
Files (source):
1483 B Test-E3 1387 B ├─ SConstruct 96 B └─ src 96 B └─ hello.c 1 directory, 2 files
-
SConstruct
DefaultEnvironment(tools=[]) CCFLAGS = { "Release": ['/MD', '/O2'], "Debug": ['/MDd', '/Od', '/Z7'], } LINKFLAGS = { "Release": ['/SUBSYSTEM:CONSOLE'], "Debug": ['/SUBSYSTEM:CONSOLE', '/DEBUG'], } program = "hello" program_binary = f"{program}.exe" program_srcdir = r"src" program_files = ["hello.c"] variants = [] buildtargets = [] runfiles = [] for variant in ['Debug', 'Release']: build = rf"build\{variant}" VariantDir(build, program_srcdir, duplicate=0) ccflags = CCFLAGS[variant] linkflags = LINKFLAGS[variant] env = Environment(tools=["msvc", "mslink"], MSVC_VERSION="14.3") env.AppendUnique(CCFLAGS = ccflags) env.AppendUnique(LINKFLAGS = linkflags) prog = env.Program(rf"{build}\{program}", [rf"{build}\{filename}" for filename in program_files]) alias = f"{program}-{variant}" Alias(alias, prog) variants.append(variant) buildtargets.append(alias) runfiles.append(rf"{build}\{program_binary}") env = Environment(tools=["msvs"], MSVC_VERSION="14.3") vcproj = env.MSVSProject( name = program, target = "Test" + env.subst('$MSVSPROJECTSUFFIX'), variant = variants, srcs = [rf"{program_srcdir}\{filename}" for filename in program_files], buildtarget = buildtargets, runfile = runfiles, # TODO: remaining arguments ) -
Files (after SCons):
1177023 B Test-E3 1387 B ├─ SConstruct 1037 B ├─ Test.sln 7494 B ├─ Test.vcxproj 477 B ├─ Test.vcxproj.filters 1166532 B ├─ build 1153836 B │ ├─ Debug 39936 B │ │ ├─ hello.exe 463768 B │ │ ├─ hello.ilk 7060 B │ │ ├─ hello.obj 643072 B │ │ └─ hello.pdb 12696 B │ └─ Release 10240 B │ ├─ hello.exe 2456 B │ └─ hello.obj 96 B └─ src 96 B └─ hello.c 4 directories, 11 files
-
Files (after VS Debug):
1177396 B Test-E3 1387 B ├─ SConstruct 1037 B ├─ Test.sln 7494 B ├─ Test.vcxproj 477 B ├─ Test.vcxproj.filters 168 B ├─ Test.vcxproj.user - ├─ Debug 205 B ├─ Test 205 B │ └─ Debug 205 B │ └─ Test.log 1166532 B ├─ build 1153836 B │ ├─ Debug 39936 B │ │ ├─ hello.exe 463768 B │ │ ├─ hello.ilk 7060 B │ │ ├─ hello.obj 643072 B │ │ └─ hello.pdb 12696 B │ └─ Release 10240 B │ ├─ hello.exe 2456 B │ └─ hello.obj 96 B └─ src 96 B └─ hello.c 7 directories, 13 files
Hidden folder
.vsnot included.
Could successfully "step into" debug hello.exe from VS.