Auto Discovery of build order problems in c# solutions

 

Some background:

When developing a large scale project, C# Solutions quickly become laden with too many proj files, so when loading a single monolithic solution starts slowing down developer progress, there are several approaches which can be taken:

  1. Dividing the project into several separate parts by using separate solutions which reference each other by dll reference, while using project references internally. (Multi Solution)
  2. Having one solution for compilation + small solutions per feature/team (Partitioned Single Solution)
  3. Using dll references across the board and allowing solutions to be created by need.

You can read more about the different solution construction methods and microsoft's recommendations in the article about solution construction methods on msdn.

 

In our c# solutions we use dll referencing (option 3), this method has some benefits like allowing for small solutions to be created on the fly for a team or a feature.

 

The problem

This approach has one downside: you need to maintain the solution’s internal dependency tree by yourself, by adding a dependency definition to the solution for each same-solution project who's dll is referenced, this allows the studio to construct a build order tree, which prevets a project from compiling before it's dependencies are built.

as we all know manual management of such a structure is prone to neglect which leads to intermittent build errors depending on whether you are compiling a clean build (which is always done on the build server), or have old version of dlls you can depend on (in which case other api-change related problems can arise)

 

Compiling C# in vs2012 is multi-threaded, this increases the chance for a project to be compiled before it's dependencies so the problem becomes even more apparentKnowing of such problems in advance, can prevent the build from breaking multiple times until you finally get it right.

Using solutions based on Partitioned Single Solution (option 2) will also lead you into the same dependency tree problems I faced.

 

Now, if you are thinking: “nobody builds their solution this way”, you obviously are not working on an enterprise scale c# project, and so you can count your blessings and move on to read one of my other postings, which deal with more core c# oriented matters, but as this is not the first project on which I have encountered the problem (& not the first company too), I imagine that this can help those of you who remain with me after this point.

I ended up writing a small program to detect these annoying errors for me:

 

 

 missing dependencies inspector

 

 

This detects the problem by:

  1. Mapping the assembly names of all the projects (parsing the solution + proj files)
  2. Searching through the project references to find references to dlls
  3. Taking only the dlls that are created within the solution
  4. Removing dll references that already have manually defined solution dependencies
  5. Reporting what we found.

Automatic correction is very possible, but I doubt it is worth the effort of implementing so I will leave this as an exercise for any of you who are willing and able.

 

 

This is how this is actually done in code: (look at the comments inside the code) 

  private void btnRun_Click(object sender, RoutedEventArgs e)
        {
            //parse the solution (dll refs which have a manual dependency defined are treated as project dependencies)
            SolutionInfo sln = new SolutionInfo(txtSolution.Text, ParseProjectsMethod.ParseFull);
            
            //get all assembly names in solution (lower case)
            var assemblyNames = sln.ProjectsInSolution.Select(p => p.Value.AssemblyNameOnDisk.ToLower());

            //identify dlls which were supposed to be references
            Dictionary<ProjectInfo, IEnumerable<ProjectInfo>> missingDependencies = new Dictionary<ProjectInfo, IEnumerable<ProjectInfo>>();
            
            var projects = sln.ProjectsInSolution.Values;
            foreach (var proj in projects)
            {
                //DependsOnDlls means only the dll references (if dependency was manually defined already it is considered a proj ref)
                var missingDependencyAssemblyNames = proj.DependsOnDlls.Where(dllName => assemblyNames.Contains(dllName.ToLower()));
                
                //get more data for the found problems by getting full project information for each project involved.
                if (missingDependencyAssemblyNames.Count() > 0)
                {
                    List<ProjectInfo> missingProjs = new List<ProjectInfo>();
                    missingDependencyAssemblyNames.ToList().ForEach(d => missingProjs.Add(projects.First(p => p.AssemblyNameOnDisk.ToLower() == d.ToLower())));
                    missingDependencies.Add(proj, missingProjs);
                }
            }
            
            //format the missing items as strings and display in the listbox
            MainVM vm = (MainVM)this.DataContext;
            foreach (var key in missingDependencies.Keys)
            {
                var refList = missingDependencies[key];
                foreach (var ref1 in refList)
                {
                    vm.MissingDependenciesList.Add(key.Name + " --> " + ref1.Name);
                }
            }
        }

 

About the code:

I have written my own parser for the solution and project files some time back, and I have been using them ever since for several other projects. The part about treating the dll references with manual dependencies defined is also something I’ve already done as part of the solution parsing (which contains the dependency definition when manually defined) so the rest was easy.

Download project 

 

 

I hope you find this useful, as I know I did, I would think this kind of check should have been part of the visual studio to begin with, and maybe show up as a warning or an error in order to save you the trouble of debugging these sporadic compilation order problems later on. Now you take it out for a spin, and tell me how it works for you.

 

Leave a Comment

We encourage you to share your comments on this post. Comments are moderated and will be reviewed
and posted as promptly as possible during regular business hours

To ensure your comment is published, be sure to follow the Community Guidelines.

Be sure to enter a unique name. You can't reuse a name that's already in use.
Be sure to enter a unique email address. You can't reuse an email address that's already in use.
Type the characters you see in the picture above.Type the words you hear.
Search
About the Author
I've been all over the coding world since earning my degrees have worked several years in c++ and then several in java, finally setteling i...


Follow Us
The opinions expressed above are the personal opinions of the authors, not of HP. By using this site, you accept the Terms of Use and Rules of Participation