In the beginning, our team used the standard for managing project dependencies — the GAC. Every dependency that was required for our web applications was installed on developer workstations and subsequently on servers as the application moved from development to production. I won’t belabor the reasons why this was less than ideal, but it was, none-the-less, the standard.
So Java and pretty much every other development platform that I’ve worked on manage dependencies with a finer control, and I encouraged the team to use something approaching what .Net folks call “bin deployable”. This mean creating a local dependencies folder named, appropriately enough, “Dependencies”, and stuffing most non .Net framework assemblies there. With a little effort to make sure our project references were kept as relative and assurances given to our CM folks that storing binaries in version control is a normal thing to do, we were off to the races. The list of things we had to document for the release management folks diminished and we had tighter control of what versions of dependent components were being used.
Now enter NuGet, and .Net seems to finally be joining the rest of the development world with relation to packaging. There’s a burgeoning packaging infrastructure and folks are racing to get packages for most .Net frameworks that exist into the central, public repositories. But they’re not all there yet. And some of those that are there haven’t quite gotten the hang of it. I’ve noticed a few packages that are making huge assumptions about what dependent packages someone might want (e.g. FluentNHibernate requires that you bring in all of Castle). This last fact, combined with the lack of a complete package availability causes us to have to create an internal package repository where we are housing packages that don’t exist (yet) or that need to be packaged with different dependencies (e.g. FluentNHibernate). The net result is that we can delete our Dependencies folder from most (all?) of our projects, and rely on NuGet to manage those for us.
How it works
At the very base, we have a project that houses 3rd party components that don’t yet have a home on a public NuGet repository. I’ve housed the assemblies for these components in separate folders, and use the conventional NuGet folder structure underneath each of these folders. So, for example, FluentNHibernate’s folder in this project looks like:
At the root of every folder is a .nuspec file which is bare minimum — it gives only the id, version (matching the public released version of the assembly), and basic package info. Since the folder structure is already in the “native” format, a NuGet pack will package our assembly up correctly and deploy correctly when installed. In the future, we’ll probably take a pass to determine the .Net framework runtime dependencies, but I don’t see that as being critical yet.
So now that we have these packages all ready to get packaged, we have a Jenkins (nee Hudson) job which runs through each top level folder and runs
nuget pack somepackage.nuspec. This produces .nupack files which are copied to a shared repository location on a “deployment” server. This shared repository is actually part of a NuGet repository web application, so our internal repository looks and acts like the main public one — we’re not just using windows shares. That NuGet repository application is itself just another web project, built using Jenkins.
Another convention we’ve adopted, which is yet another idea stolen from every-other development system out there, is the notion of build support files. For each project or solution, we create a folder called “BuildSupport” which houses either msbuild scripts that are included by each project, or other batch files that are used for various steps such as deployment (or, in this case, invocations of nuget pack). One example of such a ancillary build script is generation of our assembly versions. For each project, we migrate the following assembly properties from $projectroot/Properties/AssemblyInfo.cs into a new file called $projectroot/Properties/AssemblyVersion.cs :
[assembly: AssemblyVersion("18.104.22.168")] [assembly: AssemblyFileVersion("22.214.171.124")] [assembly: AssemblyConfiguration("Debug")]
Then, each project includes a file $solutionroot/BuildSupport/Common.targets file which houses the following target:
<Target Name="AssignVersion"> <Message Text="Version: $(Major).$(Minor).$(Revision).$(Build_Number)" /> <MSBuild.Community.Tasks.AssemblyInfo CodeLanguage="CS" OutputFile="Properties\AssemblyVersion.cs" AssemblyFileVersion="$(Major).$(Minor).$(Revision).$(Build_Number)" AssemblyVersion="$(Major).$(Minor).$(Revision).$(Build_Number)" AssemblyConfiguration="$(Configuration)" Condition="$(Build_Number) != '' " /> </Target>
The AssemblyVersion.cs file as it sits on the source has some arbitrary version information inside, but during our Jenkins builds, the version number components are passed as variables (from the build itself), and our resulting assembly binaries have versioning in them. For versioning, we’ve adopted the strategy most prevalent in the rest of the development world: Major.Minor.Revision.Build, and are careful to distinguish upon which release boundary breaking changes are made. The net result of this assembly version hack is that for developer builds on local desktops, nothing is done and the debug builds typically produced there have some arbitrary numbers. For release builds (including those that will make their way back onto dev workstations via NuGet dependencies), we track the version numbers very carefully.
For our own core frameworks, I spent some time refactoring these frameworks into smaller projects, as well as made nuspecs for these projects. Each of these new packages themselves refers to either other internal packages or public packages, completely removing the need for any “Dependencies” folder. This means dependency management is now being assisted by the NuGet tools and infrastructure, and we should have an easier time of managing dependencies. And with the work I’ve done with refactoring some of the 3rd party components, we should be “bin deployable” with only the dependencies we really need.
For our core project, we package our dependencies via the following .Bat file:
cd %1 cd Base nuget pack Base.csproj copy *.nupkg %2 cd %1 cd Logging nuget pack Logging.csproj copy *.nupkg %2 cd %1 cd MVC nuget pack MVC.csproj copy *.nupkg %2 cd %1 cd Build nuget pack Build.csproj copy *.nupkg %2 cd %1 cd Boot nuget pack Boot.csproj copy *.nupkg %2 cd %1 cd Auth nuget pack Auth.csproj copy *.nupkg %2 cd %1 cd Messaging nuget pack Messaging.csproj copy *.nupkg %2 cd %1 cd Persistence nuget pack Persistence.csproj copy *.nupkg %2 cd %1 cd Validation nuget pack Validation.csproj copy *.nupkg %2
This results in packages that track their version number with the underlying assembly, have a version number which is managed by the continuous integration server (Jenkins), and automatically become available for downstream consumers (our business web applications) via our internal repository.