4

I am analysing a Windows Forms application in .NET Framework 4.5.2 with 4 separate solutions with a combined 1.5million lines of code (and 10 years of development)

  • Libraries.sln (54 projects)
  • Tools.sln (18 projects with many depending on 28 projects in Libraries)
  • MainApp.sln (140 projects with many depending on 28 projects in Libraries)
  • MainAppTest.sln (60 projects with dependencies on many projects in MainApp)

All references currently are hardcoded to specific dll's in corresponding bin/debug folder

Currently it is only possible to do a build on 1 specific machine (with the correct drive mapping and folder structure)

Architectural Thoughts

  • Put everything into a single solution and reference projects (so no direct referencing of build dlls). Could use solution folders to virtually segregate areas (Libraries, Tests, Tools)
  • Keep the existing 4 solutions and start by using a private NuGet feed for the 28 shared Libraries (I tried this using Artifacts on Dev Ops and it works fine - however getting into complexity regarding whether to include pdb debugging symbols in the package)
  • Pack multiples projects (eg the 28 shared libraries) into a single NuGet package - this has issues https://github.com/NuGet/Home/issues/3891

Question

With today's modern machines and fast SSD's, is there any reason not to have a single massive solution (270 projects, 1.5m loc) for a solution which doesn't currently have code reuse anywhere else?

Update 1

Thank you all I really appreciate your time and answers.

  • The problem I'm trying to resolve (and good to get me to define it) is getting the build working on another machine, so we can then automate the release process which is manual (10 hours of builds/running 11,000 tests/building the Inno installer). I imagine we will use Azure DevOps for CI/CD.

  • I believe the Libraries project changes sometimes, but the client wants to be able to step into the code when debugging, so can get clarity in case the issue is in there.

  • Unknown as to the full clean build time of all the solutions (as I am leaving alone the only working machine which would take significant effort to rebuild)
  • Good idea on a build script (if it comes to that)

Update 2

All the responses have pertinent points - thank you again.

  • 9
    What specific problem are you trying to solve? – Robert Harvey Mar 18 at 13:46
  • How often to the projects change in each of the existing solutions? – Greg Burghardt Mar 18 at 16:56
  • Option #4: Keep the four solutions but write a single build script (e.g. using msbuild) that builds all four of them in a correct and consistent order. – John Wu Mar 18 at 19:05
  • 1
    Just put the 270 projects into one solution and measure the times for usual tasks like (a) opening the solution in VS, (b) building the project, (c) start up the debugger, (d) make a global search through the whole project (e) "find references", or some other tasks you can think of. Then come back and write an answer to your question (I would be interested in the outcome, too). – Doc Brown Mar 19 at 9:07
  • 1
    Note that multiple VS solutions can reference the same projects. It's perfectly possible to have many solutions for a single codebase, e.g. a solution with the DAL projects, one with the BLL projects, and one per bounded context which includes the DAL and BLL projects that belong to the specific bounded context, while also having a massive solution that houses everything. You can categorize it as much as you want, which can really help with keeping your IDE more performant in cases where you only need to work with a subset of projects at a given time. – Flater Mar 20 at 12:43
5

As with everything, there are trade-offs. There is one project my team has to develop that is similar, but in some ways worse. All the project names look similar, and the folders they are shoved in to don't make sense. After all this time, your set of solutions likely contain dead and unused code.

Step One

Make sure everything is in version control. If you cannot revert to a known good state you will find yourself in a really bad spot.

Step Two

Remove projects that are not referenced by anything. Of course exclude the desktop, command line, and web applications in the solution. But culling the cruft helps minimize what you have to deal with next.

For example, if only 28 libraries out of 56 defined in the Libraries.sln then all the remaining projects are not needed to run. Get rid of them (you can always resurrect them from version control).

Step Three

Pick your poison: Either define an absolute path to push the compiled libraries to, or package them up in Nuget and publish to a local Nuget server. If time permits, I highly recommend the latter. It is much less fragile.

Step Four

Adapt your projects to use the libraries published in the manner defined in Step three.

When All that is done...

Now you are in a better position to determine if it is easier or better to manage these as separate projects or one large one.

The big issue with one large solution is it is very unwieldy to manage in Visual Studio. You'll need to add some organization with folders, but take care in how you name them and organize things or you can't find what you are looking for.

As to merging projects within a solution (i.e. making 2 libraries into one), I recommend against that until you are aware of the impacts to other projects.

If your libraries are deployed via Nuget, then I recommend several projects.

| improve this answer | |
  • Much appreciated @Berin. The step by step is a very good guide. – Dave Mateer Mar 19 at 10:04
4

I find structure you describe as extremely problematic. I understand why someone would make this kind of project structure. But I believe that it is just a workaround the real problem, not a solution.

Here is rule of thumb : Keep assemblies that are changed often together in a single solution. And if a project changes often, keep dependant project in the same solution.

This means that if you make a change to project A in Solution1 and it forces you to switch to Solution2 to make sure it can be built properly, then you have a problem. And best solution is to keep those projects together in a single solution.

When you split of an assembly as a external library to a solution, you must make sure that libary's API is stable and high-quality. Having to use a library with bad API and not being able to easily change the API is highly detrimental to quality of the overall design. If you do belive library's API is stable and high-quality, creating a NuGet package out of it should make things much easier for you.

My personal experience is that developers rarelly create such an API between projects to make splitting projects as libraries trully viable. Creating a good library API takes lots of deliberate effort and skill. It is not something that happens naturally and organically. Especially on big and old projects like yours. If I were to bet, none of the libraries on your project are worthy to keep as separate NuGet packages.

All references currently are hardcoded to specific dll's in corresponding bin/debug folder

Yeah. Don't this. Ever. It forces you to use "Project dependencies" to make sure projects build in the right order. And it is PITA to keep this settings correct. When a direct project reference or NuGet reference would make things trivial.

Currently it is only possible to do a build on 1 specific machine (with the correct drive mapping and folder structure)

I don't care what you do. FIX THIS! This is a huge red flag that structure is wrong.

MainApp.sln and MainAppTest.sln

Now this is obviously wrong. Tests always change when the code they test changes. Keeping them in separate solutions instantly breaks the above rule of thumb. An assembly and it's test assembly should always be in same solution. I see no way how they could comfortably exist in different solutions.

| improve this answer | |
  • Thanks @Euphoric - ROT: "Keep assemblies that are changed often together in a single solution". Nice. – Dave Mateer Mar 19 at 10:11
  • "If I were to bet, none of the libraries on your project are worthy to keep as separate NuGet packages." :-) But ultimately I agree that most attempts at Library abstractions I've seen over many years have led to unnecessary complexity. And complexity is what I'm usually trying to alleviate – Dave Mateer Mar 19 at 10:14
2

Some excellent answers here already, but let me focus on what you mentioned to be your main issue, getting the build working on another machine.

This should be mostly orthogonal to any of the other issues you experience with the current project / solution structure. If that is your top priority, I would recommend to solve this first, without cleaning up the overall structure too much. Merging everything into one solution is nothing which brings you nearer to your main goal.

As @JohnWu mentioned in a comment, start with a build script. This script should

  • initialize some environment variables required (maybe detecting in which folders the required tools are installed)

  • check if required drive folders are available (or other prerequisites)

  • run msbuild.exe (or devenv.exe) in the correct order for the four solutions, with the correct configurations (and include detection if one of the build fails). To give you an idea how this could look like, here is an excerpt of one of our build scripts:

    %MSBUILD% /m /p:Configuration=Release %MYFOLDER%\MySolution.sln >MyBuild.log
    if ERRORLEVEL 1 goto :displayerror
    find "0 Warn" MyBuild.log >nul:
    if ERRORLEVEL 1 goto :displayerror
    
  • copy all the EXE files, DLLs, documents, configuration templates required for the release process into a "Deploy" folder (and not more).

I would recommend to make sure you have some standard where the required build tools are installed (for example, the folder where Visual Studio is placed). The build script can mitigate some differences between different environments, but you should not make things harder for it than necessary.

Now, your goal should be that you can checkout the whole source tree on a arbitrary machine, in some folder, run that script and get the build done.

For this, it is important that you eliminate all absolute path references from the VS projects. Every reference, every pre-build or post-build event should use relative paths, whereever possible. For this, it does not matter if you use "project references" or DLL references, both must be relative paths.

If you cannot eliminate all absolute references in some of the prebuild or postbuild events, you may utilize the environment variable initialization from the build script for this task. Just put these into a helper script of its own, and call that script where it is needed.

Finally, I would heavily recommend to make sure your projects don't contain any configurations not required. Use Visual Studio's configuration manager to find out about which configurations are there, and delete everything you don't need. For example, if you only need the standard "AnyCPU" config, make sure there are no "x86" and "x64" configurations any more (if you have different configs, Visual Studio tends to open "the wrong one" by default, and you may run into trouble when you run the next build by the IDE).

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.