Source Structure (Part 2): The reference problem, or …
... trying to make up for yesterday's post.
Sorry, for yesterday's first part. I guess it does actually represent pretty well how torn I am. Even the damn post is non-linear. ![]()
So, I guess I really do have to write down all the options, all the pitfalls and all the optimizations I see. Maybe that will clear up my vision. Oh, and I would love to hear from you, how you solve this mesh of interdependency not just between projects, but also between tools - and all that under the strict goal to maximize productivity and obstruct the flexibility as little as possible. And flexibility in my sense of the word means as well that it should enable me, if needed, to improve on dependant frameworks as closely as possible (direct project reference in my solution) and if not needed allows me to save build time by reference a specific version of said framework by assembly reference.
Since this reference problem is as good a starting point as any other of the problems I see when thinking about source structures and source management, I'll start my discussion right here, work my way through this problem set and come up with followup problems.
The reference problem
I guess it is time for some images to accompany my requirements. What did I mean by direct project reference versus assembly reference. Pretty simple:
Direct project reference:
Assembly reference:
So, direct project referencing allows me to make any changes to the framework directly, without switching to a different Visual Studio. This situation is ideal for starting the development of the framework or for hunting down bugs. It will greatly improve my productivity in those two scenarios, because here I can closely look at the relationship between my application code and the framework code. And altering the framework code and observing the effect on the application is as easy as can be.
But since it is a framework it should be a slowly changing part of your overall application and assembly landscape. That's why I need the second mode as well, where I just want to reference a precompiled assembly. That has two reasons: One it saves on compile time, because the IDE does not have to check if anything changed, etc. Thus improving my productivity while developing my application. And two it prevents me from accidentely modifying a framework or compiling against an old version or the trunk version, just because I did not pay close attention to what I checked out of source control. Don't underestimate the second argument! Anybody who has spent time hunting down a weird bug - until discovering that he didn't check out the correct version of a framework, will tell you about sever self-inflicted bruises from banging heads on desks, etc. So I believe the second argument is actually the stronger one of the two, because it saves me from wasting time and energy on checking and rechecking if I use the right framework. Plus it is there simply because I am worried about my (and your) health.
Can we automate that transition? Digging into the .sln and .csproj files.
Please don't pay any attention to the file paths here. I will discuss it later, why these filepaths displayed here are actually very, very evil.
Direct project reference:
This results in following SvnPlanning.WpfSampleApplication.sln:
- Microsoft Visual Studio Solution File, Format Version 10.00
- # Visual Studio 2008
- Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SvnPlanning.WpfSampleApplication", "SvnPlanning.WpfSampleApplication\SvnPlanning.WpfSampleApplication.csproj", "{A9CDAFB6-9224-4064-9A04-01C68C957633}"
- EndProject
- Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_Framework", "_Framework", "{25121969-C024-446A-9CED-92ABD1200DE5}"
- EndProject
- Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Com.Hertkorn.Framework.SampleFramework", "..\Com.Hertkorn.Framework\Com.Hertkorn.Framework.SampleFramework\Com.Hertkorn.Framework.SampleFramework\Com.Hertkorn.Framework.SampleFramework.csproj", "{8EEAC483-B09F-4771-8A29-EFCC01B029FA}"
- EndProject
- Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {A9CDAFB6-9224-4064-9A04-01C68C957633}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {A9CDAFB6-9224-4064-9A04-01C68C957633}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {A9CDAFB6-9224-4064-9A04-01C68C957633}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {A9CDAFB6-9224-4064-9A04-01C68C957633}.Release|Any CPU.Build.0 = Release|Any CPU
- {8EEAC483-B09F-4771-8A29-EFCC01B029FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {8EEAC483-B09F-4771-8A29-EFCC01B029FA}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {8EEAC483-B09F-4771-8A29-EFCC01B029FA}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {8EEAC483-B09F-4771-8A29-EFCC01B029FA}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- GlobalSection(NestedProjects) = preSolution
- {8EEAC483-B09F-4771-8A29-EFCC01B029FA} = {25121969-C024-446A-9CED-92ABD1200DE5}
- EndGlobalSection
- EndGlobal
And following SvnPlanning.WpfSampleApplication.csproj:
(I skipped unnecessary parts. They are marked by [...])
- <?xml version="1.0" encoding="utf-8"?>
- <Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- [...]
- <ItemGroup>
- <Reference Include="System" />
- <Reference Include="System.Core">
- <RequiredTargetFramework>3.5</RequiredTargetFramework>
- </Reference>
- <Reference Include="System.Xml.Linq">
- <RequiredTargetFramework>3.5</RequiredTargetFramework>
- </Reference>
- [...]
- </ItemGroup>
- <ItemGroup>
- <ProjectReference Include="..\..\..\Com.Hertkorn.Framework\Com.Hertkorn.Framework.SampleFramework\Com.Hertkorn.Framework.SampleFramework\Com.Hertkorn.Framework.SampleFramework.csproj">
- <Project>{8EEAC483-B09F-4771-8A29-EFCC01B029FA}</Project>
- <Name>Com.Hertkorn.Framework.SampleFramework</Name>
- </ProjectReference>
- </ItemGroup>
- [...]
- </Project>
Assembly reference:
This results in following SvnPlanning.WpfSampleApplication.sln:
- Microsoft Visual Studio Solution File, Format Version 10.00
- # Visual Studio 2008
- Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SvnPlanning.WpfSampleApplication", "SvnPlanning.WpfSampleApplication\SvnPlanning.WpfSampleApplication.csproj", "{A9CDAFB6-9224-4064-9A04-01C68C957633}"
- EndProject
- Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "_Framework", "_Framework", "{25121969-C024-446A-9CED-92ABD1200DE5}"
- EndProject
- Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Any CPU = Debug|Any CPU
- Release|Any CPU = Release|Any CPU
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {A9CDAFB6-9224-4064-9A04-01C68C957633}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {A9CDAFB6-9224-4064-9A04-01C68C957633}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {A9CDAFB6-9224-4064-9A04-01C68C957633}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {A9CDAFB6-9224-4064-9A04-01C68C957633}.Release|Any CPU.Build.0 = Release|Any CPU
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
- EndGlobal
And following SvnPlanning.WpfSampleApplication.csproj:
- <?xml version="1.0" encoding="utf-8"?>
- <Project ToolsVersion="3.5" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
- [...]
- <ItemGroup>
- <Reference Include="Com.Hertkorn.Framework.SampleFramework, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
- <SpecificVersion>False</SpecificVersion>
- <HintPath>..\..\Com.Hertkorn.Framework\Com.Hertkorn.Framework.SampleFramework\Com.Hertkorn.Framework.SampleFramework\bin\Debug\Com.Hertkorn.Framework.SampleFramework.dll</HintPath>
- </Reference>
- <Reference Include="System" />
- <Reference Include="System.Core">
- <RequiredTargetFramework>3.5</RequiredTargetFramework>
- </Reference>
- <Reference Include="System.Xml.Linq">
- <RequiredTargetFramework>3.5</RequiredTargetFramework>
- </Reference>
- [...]
- </ItemGroup>
- [...]
- </Project>
As you can see, it is quite straight forward to switch between the two modes. When switching from direct referencing to assembly referencing the tool has to remove stuff from the .sln and remove a ProjectReference and add a Reference in the .csproj. The switch from assembly referencing to direct referencing is equally straight forward. Btw: Right now I don't care about Visual Studio integration of the tool, I am fine with unloading the solution, running the tool to switch mode and re-opening the solution.
From looking at the stuff that needs to change between the two mode we can derive what the tool needs to know:
- The location of the .sln
- The location of the two .csproj
- The GUIDs of the two .csproj
- The mapping that SvnPlanning.WpfSampleApplication.sln contains "{A9CDAFB6-9224-4064-9A04-01C68C957633}" (SvnPlanning.WpfSampleApplication.csproj)
- The mapping that version a.b.c.d of "{A9CDAFB6-9224-4064-9A04-01C68C957633}" depends on version e.f.g.h of "{8EEAC483-B09F-4771-8A29-EFCC01B029FA}" (Com.Hertkorn.Framework.SampleFramework.csproj")
With that information alone it could tailor the .sln and the .csproj to either reflect mode A or mode B.
Decisions necessary to support this tool (= TODO list)
- Do we check in the ever changing .sln and .csproj files into subversion?
- Do we only check in the meta data that is needed to create the .sln and .csproj files?
- Do we only check in .sln and .csproj in a certain mode, e.g. only in assembly reference mode?
Related decisions
- Where should the precompiled assemblies of the framework lie, directly inside the application path, e.g. in a lib directory or in a special assemblies folder structure?
- Do we use the GAC as a very special assembly folder structure?
- Who is responsible for making sure that those assemblies exist, are up to date and contain the latest bugfixes?
- How does this play out on the continuous integration server?
- Can we get debugging information even for precompiled assemblies?

Interesting.
I use assembly references instead of project references for the same reasons you posted. One thing you might want to consider is using the project configuration to deturmine the assembly hint path:
..\..\Com.Hertkorn.Framework\Com.Hertkorn.Framework.SampleFramework\Com.Hertkorn.Framework.SampleFramework\bin\$(Configuration)\Com.Hertkorn.Framework.SampleFramework.dll
Bro num
Comment on September 1, 2009 @ 12:47:46
*determine*
Comment on September 1, 2009 @ 12:48:55