scons icon indicating copy to clipboard operation
scons copied to clipboard

Multiple-variants MSVS project doesn't work

Open lilveg opened this issue 4 years ago • 7 comments

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

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>

lilveg avatar Apr 07 '21 20:04 lilveg

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 avatar Apr 07 '21 20:04 bdbaddog

@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,
)

jcbrill avatar Oct 16 '24 16:10 jcbrill

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?

mwichmann avatar Oct 16 '24 16:10 mwichmann

If you do the latter,

Which example is "latter" referring to:

  1. Conceptually this example
  2. Equivalent example, or
  3. 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.

jcbrill avatar Oct 16 '24 17:10 jcbrill

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.

mwichmann avatar Oct 16 '24 17:10 mwichmann

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.

jcbrill avatar Oct 16 '24 17:10 jcbrill

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:

  1. Files (source):

    1483 B Test-E3
    1387 B ├─ SConstruct
      96 B └─ src
      96 B    └─ hello.c
    
    1 directory, 2 files
    
  2. 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
    )
    
  3. 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
    
  4. 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 .vs not included.

Could successfully "step into" debug hello.exe from VS.

jcbrill avatar Oct 16 '24 21:10 jcbrill