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

Feature request: Include full solution file header to enable Microsoft Visual Studio Version Selector to pick correctly #308

Closed
craigktreasure opened this issue Nov 18, 2021 · 9 comments · Fixed by #314
Labels
feature request New feature or enhancement

Comments

@craigktreasure
Copy link
Member

The solution file generated lacks the full file header for a solution file (see here).

The solution file header that gets generated is essentially just the file format version:

Microsoft Visual Studio Solution File, Format Version 12.00

It would be great if it contained a full file header to empower the Microsoft Visual Studio Version Selector to pick the right version to launch when multiple version of Visual Studio are installed.

The full header is expected to look like:

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.28701.123
MinimumVisualStudioVersion = 10.0.40219.1

Even better, it would be great if SlnGen took a parameter instructing which Visual Studio version to include in the file header.

To workaround this very issue, I wrote a new SlnUp tool to update a solution file header with Visual Studio version information. However, this means I have to skip launching the solution using SlnGen so that I can run slnup 2022 against the generated solution file before launching the solution file myself. This allows the Microsoft Visual Studio Version Selector to launch the solution file using Visual Studio 2022 when multiple versions are installed.

@jeffkl jeffkl added the feature request New feature or enhancement label Nov 18, 2021
@jeffkl
Copy link
Collaborator

jeffkl commented Nov 18, 2021

I looked into this a while back but couldn't figure out a good way of determining the version of Visual Studio. If SlnGen knows which devenv.exe to use, it just calls that instead. The Visual Studio Version Selector is used by since --useshellexecute is on by default and can be disabled with --useshellexecute false. I've heard complaints about this before where the wrong instance of Visual Studio is used.

I propose that if SlnGen is running in a terminal where devenv.exe is on the PATH, it should use that one by default. If there is no devenv.exe on the PATH and the user did not specify a full path to devenv.exe manually, then shell execute should be used as a last resort instead. I think this would make things work more as expected. What does everyone think?

@craigktreasure
Copy link
Member Author

Thanks @jeffkl. I'm a bit confused by some of your statements, so forgive me if I misunderstood something.

Is SlnGen locating the Visual Studio Version Selector and executing it or is it just invoking the .sln file assuming that Visual Studio Version Selector will handle it? Not everyone has Windows configured to open .sln files using Visual Studio Version Selector. I thought I did on all my machines, but one machine was configured to open with VS 2019. So, that's one potential source of confusion.

I rarely have devenv.exe on my PATH. And, I know a bunch of people who generate the .sln and then simply double click the generated .sln expecting it to open with the appropriate version of VS for their project. However, I don't think it's a crazy idea to prefer a devenve.exe from the path.

With the .sln generated by SlnGen today, without any Visual Studio version information, Visual Studio Version Selector appears to choose the latest version of VS available. That's not always the desired behavior and is often what causes people to set a particular version of VS as the default for all .sln files. If you specify 2019 version information in the .sln, it will open with 2019 even if you have 2022 installed.

Visual Studio Version Selector seems to be the right solution, in my opinion. We just need to enable it to do its job by filling in the right information in the file header. Getting the "right" version information is the tricky part.

I don't know if something similar to what I did in SlnUp would be appropriate or not, but might be worth taking a look at. I essentially generate a json file with all the release versions of VS from the docs that SlnUp uses to resolve simple human friendly VS versions to VS build versions (2022 -> 17.0.31912.275, 2019 -> 16.11.31911.196, 16.8 -> 16.8.31019.35). I could have queried the docs at runtime, but didn't want the tool to be left broken if the docs are down or change format. SlnUp is very new and I haven't really had enough time to live with the maintenance or rough edges of such a convenience yet. I literally just recently created it to workaround this issue in SlnGen. It has also been convenient as I update non-generated .sln files to open with VS 2022.

Perhaps SlnGen doesn't need to be as thorough as I was in SlnUp. Maybe a simple --vs-version 16.11.31911.196 parameter would be sufficient. It would be super low maintenance and would give you all the information you need to emit the version information into the .sln. If you don't specify the parameter, you get the existing behavior.

Another, slightly more sophisticated version could be a --vs-version 2019 parameter that just emits the first release build version of 2019 (16.0.28729.10) information into the .sln. Maintenance on SlnGen in that case would be keeping up with new major versions of Visual Studio, which don't happen very often.

@jeffkl
Copy link
Collaborator

jeffkl commented Nov 18, 2021

I think the main thing to keep in mind is that SlnGen must load and evaluate the projects with MSBuild APIs in order to determine the transitive closure of projects and their dependencies. To do this, it must find MSBuild. On Windows machines, it currently looks for MSBuild.exe on the PATH and uses that instance of MSBuild to load projects. MSBuild.exe at the moment only ships with Visual Studio so it makes sense to use the corresponding Visual Studio instance for a given MSBuild.

The behavior of launching the SLN file itself seems to be too problematic. As you've seen, some machines have Visual Studio 2019 associated with the .sln extension, while others have Visual Studio Version Selector. But even the Visual Studio Version Selector doesn't always open the correct Visual Studio. And for SlnGen to know which version to place in the solution file, it has to find devenv.exe anyways so it can get the version. So it might as well just launch that Visual Studio.

The latest version, 8.0.0, doesn't use "shell execute" anymore. It finds a Visual Studio for the MSBuild that was used and if one is not found then an error is logged. As a backup, users can specify the full path to devenv.exe to use. The logic no longer runs the default file handler for the .sln file extension. Try it out and let me know if it behaves as you expect.

@craigktreasure
Copy link
Member Author

But, again, that only applies if you're always relying on SlnGen to launch the .sln, right? Could we get the version of devenv to stamp into the .sln that gets generated?

@craigktreasure
Copy link
Member Author

Given the changes in 8.0.0, it doesn't sounds like what i'm asking for is going to fit with the direction you're headed. I'll stick with my workaround and not rely on slngen to launch the solution for me.

Essentially:

& dotnet slngen **\*.csproj --solutionfile:$solutionFilePath --launch:false --ignoreMainProject
& dotnet slnup 2022 --path $solutionFilePath

if ($Launch) {
    Write-Host 'Launching Visual Studio...'
    & $solutionFilePath
}

@jeffkl
Copy link
Collaborator

jeffkl commented Nov 19, 2021

But, again, that only applies if you're always relying on SlnGen to launch the .sln, right? Could we get the version of devenv to stamp into the .sln that gets generated?

It sounds like the only good solution is to pass in the version you want to use? If you want SlnGen to figure out the version for you, then it needs to know which VS instance to use which sounds like is exactly the problem? There is a command-line argument to tell it which devenv.exe to use and if it finds an MSBuild.exe it will use the correct one automatically.

I think I need to understand your workflow more. You never want SlnGen to launch Visual Studio for you but you must have the version embedded in the generated solution so that later on, when someone opens the solution manually, the correct Visual Studio instance is used? Do you not use the Visual Studio Developer Command Prompt?

I am not opposed to writing a version to the solution file as an opt-in feature. SlnGen would still need to know which Visual Studio instance to use when determining the version, unless you want to have the user pass in the version every time. In my opinion, its a better user experience if everything "just works". I've tried to make SlnGen be as straightforward as possible by having it determine the build environment automatically so most users can just run it.

When you're using Visual Studio and want to do things from the command-line, the best experience is to use the Developer Command Prompt for that instance. If you have multiple instances of Visual Studio, each Developer Command Prompt has the corresponding instance of MSBuild.exe on the PATH so when you build, that version of MSBuild matches the version of Visual Studio as you'd expect. If your projects and code use newer technologies that only build in Visual Studio 2022, you wouldn't want to try and build from the command-line with MSBuild 4.0. It used to only be possible to install one instance of each version of Visual Studio and most people just installed the latest. Starting with Visual Studio 2017, you could install multiple instances side-by-side so you could run a Preview version next to a Production version. When you want to build from the command-line it is very important to use the Visual Studio Developer Command Prompt so you're using the toolset from the right instance.

Things get more complicated if you want to use a different terminal like git bash. In this case, I'm assuming you're relying on the fact that dotnet is on the user PATH. In this case, SlnGen uses the .NET SDK to load and evaluate projects. However, some project types can't be loaded and built with .NET SDK (C++, Service Fabric, Visual Studio SDK, etc). This is a problem with the .NET SDK and the way Microsoft ships this stuff, but users end up caught in the middle. You could be working on a repository with a C++ project and not realize that you can no longer build with dotnet, now you have to build with msbuild instead. dotnet itself can also be installed side-by-side and when projects are loaded, the correct instance of the .NET SDK must be located (which SlnGen respects). As far as I'm aware, there's no definitive way to map a .NET SDK instance to a Visual Studio instance. So users would still need to manually specify which one if they aren't using the built-in command-line experience.

SlnGen has to work within the constraints of the Microsoft Developer Ecosystem and I want it to be as easy to use as possible. I fully realize that its "magic" is not going to be correct in every case. I'm also very open to adding new features, even if they are have low demand. I guess I'm wondering if your developer workflow could be adjusted to make things work as you want, or if the tooling should adapt to your needs. I'm very interested to learn more if you don't mind sharing, I really appreciate the discussion.

@craigktreasure
Copy link
Member Author

I do not use the Visual Studio Developer Command Prompt. I understand the complexities between what the .NET SDK and msbuild.exe and appreciate your diligence to accommodate the various options. I was hoping that by adding version information to the .sln file, we could at least give the Visual Studio Version Selector a chance to do its job for the scenarios that would benefit from using it. As for slngen launching the solution file, if it doesn't launch the solution file in a way that could give the system (Visual Studio Version Selector) a chance to respect any version preferences, then it doesn't matter.

It sounds like the only good solution is to pass in the version you want to use?

I agree with you, which was an option I suggested as well:

Maybe a simple --vs-version 16.11.31911.196 parameter would be sufficient. It would be super low maintenance and would give you all the information you need to emit the version information into the .sln. If you don't specify the parameter, you get the existing behavior.

For the sake of simplicity, let's consider a pure .NET project. I work in a team of people with varying different preferences and skillsets (like many others). In one particular case, some don't want a solution file checked into the project at all and prefer a dirs.proj, which is fine. However, there are some people (like myself) who'd still like to be able to use Visual Studio on that project. So, i use slngen to generate a solution file with various solution items included. That file never gets checked in. It works quite nicely. Some people work from the shell and others don't. Some people just want to generate a .sln and double click to open the generated solution file. Eventually they'll close it and then double click the .sln again when they come back to it. Others want to generate the solution file from a shell and have it launched for them. I personally don't use slngen to launch my solution file every time. I generate and launch the first time, but then launch it from the shell by invoking the .sln file when opening it again. I just want to enable slngen and the system (using Visual Studio Version Selector) to be able to open the version of Visual Studio that I know is appropriate for that particular project.

If you want SlnGen to figure out the version for you, then it needs to know which VS instance to use which sounds like is exactly the problem? There is a command-line argument to tell it which devenv.exe to use and if it finds an MSBuild.exe it will use the correct one automatically.

In my particular case, I don't expect SlnGen to figure out the version for me. I honestly didn't know or care how it was generating the .sln. It would be great if it was able to take a preference and allow the system to respect that preference.

I think there are various options here, but to be specific, here are a few that I think make sense to start with:

  1. If someone provides a path to a particular devenv.exe, i think it's safe to assume that's the version they want to use to open their solution file. I think it would be appropriate to retrieve version information for that particular devenv.exe and to put it into the .sln file.
  2. In the case of a pure .NET project that doesn't need msbuild.exe and can be built on a machine that may or may not have Visual Studio installed at all by using the .NET CLI, I think it makes sense to be able to specify a particular version to be stamped into the .sln file. This would accommodate people launching the .sln using the Visual Studio Version Selector as well as tooling that requires a .sln file, like dotnet-outdated-tool and dotnet format.

@craigktreasure
Copy link
Member Author

a224cf8 indicates a strong preference for devenv.exe and the comments on that commit contradict what i'm trying to accomplish. My workaround seems appropriate given the direction slngen is headed. It doesn't prevent me from doing what I want, it just doesn't do it all for me. Which is fine.

jeffkl added a commit to jeffkl/SlnGen that referenced this issue Nov 22, 2021
jeffkl added a commit that referenced this issue Nov 22, 2021
Adds command-line argument `--vsversion`. When present, the version of Visual Studio used to launch the solution is included in the solution file header. If a value is specified, that version is used instead.

Fixes #308
@jeffkl
Copy link
Collaborator

jeffkl commented Nov 22, 2021

Okay I've added a command-line argument, --vsversion. When you specify it with no value, SlnGen takes the version of devenv.exe that would be used and places that version in the header. If you specify a version to the --vsversion argument, that version is used instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request New feature or enhancement
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants