NuGet package build a solution of projects

Have you ever created a .Net shared library solution with project references between them; and then wanted to share the projects as individual packages in a NuGet feed? When you run the NuGet ‘pack’ command on a project that has a project reference to another project in your solution, then the referenced project would get included in the package.  That isn’t what you want to happen if you plan on making that referenced project a separate package.

Bad Solution: Package References All the Time

To deploy each library as a separate package they need to have package references between them.  You can build the core assembly, and then make a post build task to copy it to an internal feed, and then reference that package from the other project.  There are a few problems with this solution.

If you are publishing to an internal file share feed, then you will have problems with performance.  Each compilation puts a new version in your feed.  Then when the consuming library does a NuGet update on the post build event, it takes longer to parse the file feed the more package files are in it.  Visual Studio nor NuGet cache metadata for file feeds; instead they reopen the compressed nupkg files on each update to check dependencies.

In order to make sure the new referenced package from your solution is used by the consuming library the build number must change on each build.  This means lots of packages in that internal feed.

The biggest problem with this approach is you lose refactoring ability.   If you refactor something in the shared library, the refactoring tooling does not realize that another project is referencing it as a package.  So the consuming library does not get refactored with the core library change.  You find out about that when the consuming library has a build error after the package is updated.

Better Solution: Build NuSpec Files by Script

Update Aug 19,2013: See ‘Best Solution’ section below for an even better solution for NuGet 2.5 or later.

You can leave project references between your projects and tweak the nuget packages as they are built for release.  You also do not need to publish each build of your project to a feed.  Just build and test as you normally would if NuGet was not involved.  Then build a custom set of MsBuild tasks to create your NuGet packages.

I. Include Community tasks

Download the MsBuild Community Tasks from Github, NuGet, and nUnit into your solution folder.  You may even want to include them as ‘solution folders’ visible within your solution.  You can see what this looks like in candor-common on github.

NuGetSolution-SolutionExplorer-SolutionItems

II. Create a Project to Update NuSpec Files

NuSpec files can reference other packages that they depend on based on the packages.config in the project folder, plus any additional packages referenced in the nuspec file.  Since this approach does not use NuGet references between the solution’s projects, we need to modify the nuspec files to add them as dependencies before the nupkg files are generated.

Create a new console application in your solution (optionally, hidden under a ‘Release’ solution folder per the screenshot) called PrepareNuSpecsForRelase.  To determine project references that are also to become NuGet packages, we need to parse the project files.  Unfortunately there is no public API by Microsoft to do this, but John Leidegren answered a question on stackoverflow with a way to read solution and project files.  Add a class called ‘Solution’ with this code in the PrepareNuSpecsForRelase project.

The Program.cs entry point of the console application needs to take in the information about the solution and the desired version number of the NuGet packages to output.

private static string SolutionPath;
private static  string SolutionFolder; 
private static string ReleaseVersion;

private static readonly XNamespace MSBuildNS =
	"http://schemas.microsoft.com/developer/msbuild/2003";

private static ILog Log { get { return LogManager.GetLogger(typeof (Program)); }}

static void Main(string[] args)
{
	try
	{
		SolutionPath = args[0];
		SolutionFolder = Path.GetDirectoryName(SolutionPath);
		ReleaseVersion = args[1];

Now we can use the Solution class created from the stack overflow answer code to get a list of all the projects.

		var solution = new Solution(SolutionPath);
		IEnumerable cSharpProjects =
			solution.Projects.Where(project => project.RelativePath.EndsWith(".csproj"));

Once we have a list of projects we need to clean up any dependencies added during the last release.  Clean up is needed in case one of the dependencies from last build is no longer a dependency, such as due to a refactoring.  This next code block  iterates over all the projects that are to become NuGet packages and removes all dependencies that are NuGet packages.  This just removes them from the dependencies node in the nuspec file; It doesn’t delete the reference in the project file.

		foreach (SolutionProject project in cSharpProjects)
		{
			Log.InfoFormat("Removing any dependencies from project {0}", project.ProjectName);
			if (IsProjectNuGetPackage(project))
			{
				Log.InfoFormat("Project {0} found to be a NuGet package project", project.ProjectName);
				RemoveNuGetDependencies(project);
			}
		}

Now we need to iterate over the projects again, and this time we want to add back in any dependencies to the nuspec file for project references where that project is also to be built into a NuGet package. We tell that by checking if it has a nuspec file in the folder matching the project name.

		foreach (SolutionProject project in cSharpProjects)
		{
			Log.InfoFormat("Setting up NuGet dependencies for project {0}", project.ProjectName);
			string projFilePath = Path.Combine(SolutionFolder, project.RelativePath);
			string projFileFolder = Path.GetDirectoryName(projFilePath);
			XDocument projFile = XDocument.Load(projFilePath);
			var projectReferences = projFile.Descendants(MSBuildNS + @"ProjectReference");
			foreach (XElement projectReference in projectReferences)
			{
				if (IsReferencedProjectNuGetPackage(projFileFolder, projectReference) &&
					IsProjectNuGetPackage(project))
				{
					Log.InfoFormat("NuGet project {0} found to be dependent on NuGet project reference {1}", project.ProjectName, projectReference.Value);
					EnsureProjectIsNugetDependency(projFilePath, projectReference, project);
				}
			}
		}
	}
	catch (Exception e)
	{
		Log.Error(e);
	}
}

The code above calls into a bunch of other support methods listed below. All of these methods are making some assumptions. First, all projects in the solution that are packaged for NuGet will have a nuspec file of the same name as the project file, where the only difference is swapping out the .csproj extension with a .nuspec extension.

Notice this code does not add dependency nodes for other NuGet packages that your projects may reference. Those are read by the NuGet pack command from your packages.config.  Since the project references within this solution are not package references, NuGet will not know it will eventually be a NuGet package. Maybe a future version of NuGet will know this, and you won’t need to follow the directions in this article?

These helper methods in the console application are just XML DOM manipulation following the assumptions above, so I won’t explain them in detail.

private static void RemoveNuGetDependencies(SolutionProject project)
{
	string projFilePath = Path.Combine(SolutionFolder, project.RelativePath);
	string nuspecPath = GetExpectedNuGetFileFullPath(projFilePath);
	XDocument nuspecDocument = XDocument.Load(nuspecPath);

	XElement dependenciesElement =
		nuspecDocument.Descendants("dependencies").SingleOrDefault();

	if (dependenciesElement != null)
	{
		dependenciesElement.Remove();
		nuspecDocument.Save(nuspecPath);
	}

}

private static void EnsureProjectIsNugetDependency(string projFilePath, XElement dependencyToAdd, SolutionProject dependentProject)
{
	var dependentProjectNuspecPath = GetExpectedNuGetFileFullPath(projFilePath);
	var dependencyProjectNuspecPath = GetNuspecPath(Path.GetDirectoryName(projFilePath),
	                                                dependencyToAdd);
	string packageId = GetNuGetPackageId(dependencyProjectNuspecPath);

	XDocument nuspecDocument = XDocument.Load(dependentProjectNuspecPath);

	// First get the dependencies node
	XElement dependenciesElement =
		nuspecDocument.Descendants("dependencies").SingleOrDefault();

	if (dependenciesElement == null)
	{
		XElement metadataElement =
			nuspecDocument.Descendants("metadata").SingleOrDefault();

		if (metadataElement == null)
			throw new ArgumentException(
				"Invalid nuspec file.  It is missing its metadata element");

		dependenciesElement = new XElement("dependencies",
			new XElement("dependency", new XAttribute("id", packageId), new XAttribute("version", ReleaseVersion)));

		metadataElement.Add(dependenciesElement);
	}
	else
	{
		XElement dependencyElement =
			dependenciesElement.Elements("dependency").SingleOrDefault(
				element => element.Attribute("id").Value == packageId);

		if (dependencyElement != null)
		{
			dependencyElement.Attribute("version").Value = ReleaseVersion;
		}
		else
		{
			dependencyElement = new XElement("dependency", new XAttribute("id", packageId), new XAttribute("version", ReleaseVersion));					
			dependenciesElement.Add(dependencyElement);
		}
	}

	nuspecDocument.Save(dependentProjectNuspecPath);
}

private static string GetNuGetPackageId(string nuspecPath)
{
	Log.InfoFormat("Inside GetNuGetPackageId - Loading nuspec at {0}", nuspecPath);
	XDocument nuspecDocument = XDocument.Load(nuspecPath);
	XElement idElement = nuspecDocument.Descendants("id").SingleOrDefault();
	if (idElement == null)
		throw new ArgumentException(
			"Invalid nuspec file.  Metadata does not include an id element");

	return idElement.Value;
}

private static string GetNuspecPath(string projFileFolder, XElement projectReference)
{
	string projectRelativePath = projectReference.Attribute(@"Include").Value;
	string projectFullPath = Path.Combine(projFileFolder, projectRelativePath);
	string nuspecPath = GetExpectedNuGetFileFullPath(projectFullPath);
	return nuspecPath;
}

private static bool IsProjectNuGetPackage(SolutionProject project)
{
	Log.InfoFormat("Checking if project {0} is a NuGet package project", project.ProjectName);
	string projectPath = Path.Combine(SolutionFolder, project.RelativePath);
	Log.InfoFormat("Calculated projectPath = {0}", projectPath);

	return IsProjectNuGetPackage(projectPath);
}

private static bool IsReferencedProjectNuGetPackage(string projFileFolder, XElement projectReference)
{
	string referencedProjectRelativePath = projectReference.Attribute(@"Include").Value;
	string referencedProjectFullPath = Path.Combine(projFileFolder,
	                                                referencedProjectRelativePath);
	return IsProjectNuGetPackage(referencedProjectFullPath);
}

private static bool IsProjectNuGetPackage(string referencedProjectPath)
{
	var expectedNuGetFileFullPath = GetExpectedNuGetFileFullPath(referencedProjectPath);
	Log.InfoFormat("Looking for nuspec file at expected location: {0}", expectedNuGetFileFullPath);
	return File.Exists(expectedNuGetFileFullPath);
}

private static string GetExpectedNuGetFileFullPath(string referencedProjectPath)
{
	string referencedProjectFileName = Path.GetFileName(referencedProjectPath);
	string referencedProjectFolder = Path.GetDirectoryName(referencedProjectPath);

	int lengthOfFileNameWithoutCSProj = referencedProjectFileName.Length - ".csproj".Length;
	string expectedNuGetFile = referencedProjectFileName.Substring(0, lengthOfFileNameWithoutCSProj) + ".nuspec";
	string expectedNuGetFileFullPath = Path.Combine(referencedProjectFolder,
	                                                expectedNuGetFile);
	return expectedNuGetFileFullPath;
}

III. Create a Release Project File

It is best not to modify the project files for building the NuGet package release.  Doing so could slow down your frequent build and unit test flow.  Created a dedicated project file for the purpose of release means that only an intentional release process needs to go through the extra steps to build packages.  You also don’t want to end up with a NuGet release folder for every build you do during development.

A Release project file is just an MSBuild format file describing what to do at release time.  If you open it and compare it to any other .net project file, it will look very similar.  Create a project file in a ‘Release’ folder under the solution and call it “Release.proj”.  Scott Kirkland supplied a good starting point in his article on “Simple MSBuild Configuration: Updating Assemblies with a Version Number“.  Scott calls the file Build.proj, but he is only doing a build.  We are creating a release to NuGet, which is why we are calling it release.proj.

One deviation from Scott’s MSBuild file is that this file updates version attribute in a SolutionInfo.cs at the solution root, instead of in each individual project file.  In shared libraries I like to have the same version on all assemblies in the solution.  So I put that attribute one time in the SolutionInfo.cs file, and then link that file into all the projects.  Then I remove the version attribute from all the project AssemblyInfo.cs files.

The next step is to build on top of that build file and add steps for running unit tests (you have some, right?), preparing the nuspec files, and then running the ‘nuget pack‘ command.  You can also add FxCop checks by un-commenting that section.

As shown by the ‘DefaultTargets’ attribute on the ‘Project’ node, the default ‘Go’ target will be called when this project is built. Skip down to that section to see what it does.  To see the whole file in one uninterrupted block, check it out on github.

<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0"
		 DefaultTargets="Go"
		 xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
	<Import Project="$(MSBuildProjectDirectory)\..\Build\OpenSource\NuGet\Build.tasks" />

	<PropertyGroup>
		<Configuration Condition="'$(Configuration)' == ''">Release</Configuration>
		<SharedRoot>$(MSBuildProjectDirectory)\..</SharedRoot>
		<NuGetDropRoot>$(SharedRoot)\NuGetRelease</NuGetDropRoot>
		<NuGetDirectory>$(SharedRoot)\.nuget</NuGetDirectory>
		<UtilitiesDropDirectory>$(SharedRoot)\Utilities</UtilitiesDropDirectory>
		<BuildDropDirectory>$(SharedRoot)\bin</BuildDropDirectory>

		<MSBuildCommunityTasksPath>$(MSBuildProjectDirectory)\..\Build\OpenSource\MSBuildCommunityTasks</MSBuildCommunityTasksPath>
		<NUnitPath>$(MSBuildProjectDirectory)\..\Build\OpenSource\NUnit</NUnitPath>
		<PrepareNuSpecsProj>$(SharedRoot)\PrepareNuSpecsForRelease\PrepareNuSpecsForRelease.csproj</PrepareNuSpecsProj>

Replace these next three projects and the following Unit test project with the relative paths of your shared libraries and name of your unit tests project.  You can have more than one unit test project.  Just check out where the $(UnitTestsProj) variable is used elsewhere in this build file to see what you need to do.  You can also have as many shared libraries as you want in this build file.  Just make sure any you add/remove are reflected in the other sections of this file below.

		<CoreProj>$(SharedRoot)\Candor\Candor.csproj</CoreProj>
		<SecurityProj>$(SharedRoot)\Candor.Security\Candor.Security.csproj</SecurityProj>
		<SecuritySqlProj>$(SharedRoot)\Candor.Security.SqlProvider\Candor.Security.SqlProvider.csproj</SecuritySqlProj>

		<UnitTestsProj>$(SharedRoot)\Candor.UnitTests\Candor.UnitTests.csproj</UnitTestsProj>

		<AllProjects>

Replace these first three projects with however many shared libraries you want from your solution. They must reference the same projects you replaced above. You should rename these and the corresponding nodes they reference above. But also remember to replace that new name in the whole file. There are other places below to update also.

			$(CoreProj);
			$(SecurityProj);
			$(SecuritySqlProj);
			$(UnitTestsProj);
		</AllProjects>
	</PropertyGroup>
	<Import Project="$(MSBuildCommunityTasksPath)\MSBuild.Community.Tasks.Targets"/>
	<PropertyGroup>
		<!-- MajorVersion, MinorVersion, and Build are passed in from the command line -->
		<VersionStartYear>2011</VersionStartYear>
		<Revision>$([MSBuild]::Add(1, $([MSBuild]::Subtract($([System.DateTime]::Now.Year), $(VersionStartYear)))))$([System.DateTime]::Now.ToString("MMdd"))</Revision>
		<Version>$(MajorVersion).$(MinorVersion).$(Build).$(Revision)</Version>
	</PropertyGroup>
	<ItemGroup>
		<RegexTransform Include="$(SharedRoot)\SolutionInfo.cs">
			<Find>\d\.\d+\.\d+\.\d+</Find>
			<ReplaceWith>$(Version)</ReplaceWith>
		</RegexTransform>
	</ItemGroup>

The Go Target runs each of the other sections in order as shown in the ‘DependsOnTargets’ attribute. CheckFxCop is currently disabled because that section is commented out. The first task run is the ‘UpdateVersion’ which is described in Scott Kirkland’s article.  Next the EnsureNuSpecPreparationToolBuilt, PrepareNuSpecs, and at the very end we RunTests and then finally BuildPackages.  The other steps ensure folders are created and build the project, and optionally run FxCop when uncommented.

	<Target Name="Go"
			DependsOnTargets="CheckFxCop; UpdateVersion; EnsureNuSpecPreparationToolBuilt; PrepareNuSpecs; CreateDropDirectories; Build; ReportFxCopResults; RunTests;  BuildPackages">
	</Target>

	<Target Name="CheckFxCop">
		<!--
		<Error
			Condition="!Exists('$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\CodeAnalysis\fxcoptask.dll') "
			Text="FxCop not found at $(MSBuildExtensionsPath)\Microsoft\VisualStudio\v10.0\CodeAnalysis\fxcoptask.dll. Cannot proceed" />
			-->
	</Target>

	<Target Name="UpdateVersion">
		<Message Text="Updating SolutionInfo.cs version to $(Version)" />
		<RegexTransform Items="@(RegexTransform)" />
	</Target>

	<Target Name="EnsureNuSpecPreparationToolBuilt">
		<!-- Ensures the PrepareNuSpecsForRelease project has been built
		so that the PrepareNuSpecs target can execute it -->
		<MSBuild Projects="$(PrepareNuSpecsProj)"
				 Properties="OutDir=$(UtilitiesDropDirectory)\">
		</MSBuild>
	</Target>

This is where we execute the PrepareNuSpecsForRelease command line project to ensure all the NuSpec files are ready for the nuget pack command. Notice we pass in the solution file name and the desired version number of the nuget packages to match the version of this build.  Also notice above we made sure the PrepareNuSpecsForRelease project was built and ready to be run.

	<Target Name="PrepareNuSpecs">
		<PropertyGroup>
			<PrepareNuSpecsExe>$(UtilitiesDropDirectory)\PrepareNuSpecsForRelease.exe</PrepareNuSpecsExe>
		</PropertyGroup>
		<Exec Command=""$(PrepareNuSpecsExe)" "$(SharedRoot)\CandorCore.sln" "$(Version)"" />
	</Target>

Here is the standard build node found in all projects. It references out $(AllProjects) node above defining which projects we need built for the nuget packages.

	<Target Name="Build">
		<MSBuild Projects="$(AllProjects)"
				 Targets="Build"
				 Properties="EnableCodeAnalysis=true;OutDir=$(BuildDropDirectory)\"
				 >
			<Output TaskParameter="TargetOutputs"
					ItemName="BuildOutput"/>
		</MSBuild>
		<Copy SourceFiles="@(BuildOutput)"
			  DestinationFolder="$(BuildDropDirectory)" />
	</Target>

If the following unit tests fail, then the nuget release will not continue and there will be no packages created. If you really don’t have any unit tests (shame on you), then go ahead and comment out all the contents of the RunTests node.

	<Target Name="RunTests">
		<ItemGroup>
			<TestAssemblies Include="$(BuildDropDirectory)\*.UnitTests*.dll"
							Exclude="*.config;*.pdb"/>
		</ItemGroup>
		<NUnit ToolPath="$(NUnitPath)" 
			   Assemblies="@(TestAssemblies)" 
			   OutputXmlFile="testresults.xml"
			   ErrorOutputFile="testerrors.log"/>
	</Target>

Shame on me, I am not using FxCop. If you are then just un-comment the contents of the ReportFxCopResults node, and don’t forget the CheckFxCop node above also.

	<Target Name="ReportFxCopResults">
		<!--
		<ItemGroup>
			<FxCopOutputItems Include="$(NuGetRoot)\**\*.dll.CodeAnalysisLog.xml" />
		</ItemGroup>
		<Message Text="##teamcity[importData type='FxCop' path='%(FxCopOutputItems.Identity)']" />
		-->
	</Target>

	<PropertyGroup>
		<NugetDropDirectory>$(NuGetDropRoot)\Candor_Core_v$(Version)</NugetDropDirectory>
	</PropertyGroup>
	<Target Name="CreateDropDirectories">
		<!-- Ensure any needed output directories exist -->
		<MakeDir Directories="$(NuGetDropDirectory)"
				 Condition="!Exists('$(NuGetDropDirectory)')" />
		<RemoveDir Directories="$(BuildDropDirectory)"
				   Condition="Exists($(BuildDropDirectory))" />
		<MakeDir Directories="$(BuildDropDirectory)"
				 Condition="!Exists('$(BuildDropDirectory)')" />
	</Target>

Here is the third and final location where you need to update this MSBuild file with the names of the projects in your solution that are to become NuGet packages. The $(…) section of each Exec node after the ‘pack’ command is where you need to update with the names of the nodes you created above to represent those projects.

	<Target Name="BuildPackages">
		<PropertyGroup>
			<NuGetExe>$(NuGetDirectory)\NuGet.exe</NuGetExe>
		</PropertyGroup>
		<Exec Command=""$(NuGetExe)" pack "$(CoreProj)" -o "$(NuGetDropDirectory)" -Build -p Configuration=Release -Symbols"
			  WorkingDirectory="$(NuGetDropDirectory)" />
		<Exec Command=""$(NuGetExe)" pack "$(SecurityProj)" -o "$(NuGetDropDirectory)" -Build -p Configuration=Release -Symbols"
					WorkingDirectory="$(NuGetDropDirectory)" />
		<Exec Command=""$(NuGetExe)" pack "$(SecuritySqlProj)" -o "$(NuGetDropDirectory)" -Build -p Configuration=Release -Symbols"
					WorkingDirectory="$(NuGetDropDirectory)" />
	</Target>
</Project>

IV. Create a Release Console Command

As a start we can use Scott Kirkland’s Build.cmd. In that he gathers 2 parameters, one for build configuration and another for version number.  However, I’ll stray a little and add a separate parameter for each version part, and always assume the Release configuration.  After all, our command line project and script are called ‘Release’.

@echo Off
set majorRev=%1
if "%majorRev%" == "" (
	GOTO MESSAGE   
)

set minorRev=%2
if "%minorRev%" == "" (
	GOTO MESSAGE   
)

set patch=%3
if "%patch%" == "" (
	GOTO MESSAGE
)

:CORE
attrib -R *.* /S /D
%WINDIR%\Microsoft.NET\Framework\v4.0.30319\msbuild Release\Release.proj /p:MajorVersion="%majorRev%";MinorVersion="%minorRev%";Build="%patch%" /m /v:M /fl /flp:LogFile=release.log;Verbosity=Normal /nr:false 
GOTO END

:MESSAGE
echo.
echo.
echo Creates a new release of the Candor Core Libraries
echo.
echo Release ^<major^> ^<minor^> ^<patch^> 
echo.release
echo i.e. Release 2 1 0
echo in order to create release versioned 2.1.0.
echo.
echo Saves results to .\NuGetRelease\ 
echo.
echo.

:END

This calls msbuild in it’s install directory passing in the relative location to current where the msbuild file is located, and a parameter for each of the version attributes the msbuild file is expecting (per the comments in the build file).



So why is the build part of the version (4th place) not an option? I went along with the notion that the build number should just be based on the date. The release.proj file builds that value from the current date. The fist digit is the year marker (configurable, such as from the start of your project). The next two digits are the month, and the final two digits are the day. But it doesn’t really matter, since you should manually increment the patch, minor, or major version with every release anyway. Having something based on the date at the end helps you remember later when a given release was created.

V. What can go wrong?

You may have build errors.  Your unit tests can fail.  You may not have replaced nodes in the Release.proj file correctly, preventing the entire build from running.  But most of those problems will supply a decent error message on the command line, or test results will appear in an xml output file.  However, NuGet pack does not give a good error message on the command line.  Here is one example.

C:\Users\micha_000\Documents\GitHub\candor-common\Release\Release.proj(126,3):
error MSB3073: The command “”C:\Users\micha_000\Documents\GitHub\candor-common\Release\..\.nuget\NuGet.exe” pack “C:\Users\micha_000\Documents\GitHub\candor-common\Release\..\Candor\Candor.csproj” -o “C:\Users\micha_000\Documents\GitHub\candor-common\Release\..\NuGetRelease\Candor_Core_v0.3.1.21221″ -Build -p Configuration=Release -Symbols” exited with code 1.

To troubleshoot this you can run this command in your Nuget package manager console inside Visual Studio.  It should give you a more detailed error message.  If anyone knows of a command prompt argument for showing detail in this build script, then let me know please.  I am using NuGet 2.2.31210.9045.

The problem is usually because the nuspec file in question is malformed or one of the replacement parameters fails.  To generate this error, I emptied the Description attribute in my AssemblyInfo.cs file for this project.  For some reason NuGet does not like the description attribute in the nuspec if the Description attribute is missing a value.

From the nuspec:
$description$

From the AssemblyInfo.cs
[assembly: AssemblyDescription(“”)]

To fix this particular issue, I just added a description value and the pack command works.  After all, this is what people will see as the summary when deciding if they want to consume your package.  So put something in there.

Best Solution: NuGet 2.5 Improved project reference support

Update Aug 19,2013: As of NuGet 2.5 (April 25, 2013) projects with nuspec files and that have project references between them will now be properly referenced in the appropriate nuspec files. So you can build and debug normally with project references. Then when you want to release, just run ‘nuget pack’ against each csproj file and it will update the nuspec file with any project references before creating the package.

I will now version ‘candor common’ libraries independently. I could keep them the same by leaving the version number in SolutionInfo.cs, but now I am not required to. I found that too many releases I only changed classes in one component, but since my build process updated all package versions I was publishing the update to them all. Now I will only publish the packages that change within the solution.

The only drawback to changing the ‘candor common’ build process so far is the update of package versions in the content only packages. I have two projects that just package a controller with it’s associated models and views. The NuGet update does not handle updating those package references in the nuspec. For now I am adding the nuspec file for those MSbuild projects to the solution items to help me remember when I need to update them. If it doesn’t work out I will fix the build process using MSBuild and write about it.

My new build process has a release.cmd file as follows. Note the content packages run MSBuild to copy select files from the bootstrapper MVC project, then follow with a ‘nuget pack’ to create the package. The ‘nuget pack’ command does not work against .proj files. I also tried renaming them to .csproj files and running nuget pack, but it failed with a generic error. I think the ‘includeReferencedProjects’ is what failed, since the content only projects have no references. The script as follows does work.

md NuGetRelease

.nuget\nuget pack Candor\Candor.csproj -Build -p Configuration=Release -includeReferencedProjects -o "NuGetRelease"

.nuget\nuget pack Candor.Security\Candor.Security.csproj -Build -p Configuration=Release -includeReferencedProjects -o "NuGetRelease"

.nuget\nuget pack Candor.Security.AzureStorageProvider\Candor.Security.AzureStorageProvider.csproj -Build -p Configuration=Release -includeReferencedProjects -o "NuGetRelease"

.nuget\nuget pack Candor.Security.SqlProvider\Candor.Security.SqlProvider.csproj -Build -p Configuration=Release -includeReferencedProjects -o "NuGetRelease"

.nuget\nuget pack Candor.Tasks.ServiceProcess\Candor.Tasks.ServiceProcess.csproj -Build -p Configuration=Release -includeReferencedProjects -o "NuGetRelease"

.nuget\nuget pack Candor.Web.Mvc\Candor.Web.Mvc.csproj -Build -p Configuration=Release -includeReferencedProjects -o "NuGetRelease"

msbuild Candor.Web.Mvc.Bootstrap.ErrorHandler\Candor.Web.Mvc.ErrorHandler.proj

.nuget\nuget pack Candor.Web.Mvc.Bootstrap.ErrorHandler\Candor.Web.Mvc.ErrorHandler.nuspec -o "NuGetRelease" -verbosity detailed

msbuild Candor.Web.Mvc.Bootstrap.Security\Candor.Web.Mvc.Security.proj

.nuget\nuget pack Candor.Web.Mvc.Bootstrap.Security\Candor.Web.Mvc.Security.nuspec -o "NuGetRelease" -verbosity detailed

.nuget\nuget pack Candor.WindowsAzure\Candor.WindowsAzure.csproj -Build -p Configuration=Release -includeReferencedProjects -o "NuGetRelease"

.nuget\nuget pack Candor.WindowsAzure.Logging.Common\Candor.WindowsAzure.Logging.Common.csproj -Build -p Configuration=Release -includeReferencedProjects -o "NuGetRelease"

.nuget\nuget pack Candor.WindowsAzure.Tasks\Candor.WindowsAzure.Tasks.csproj -Build -p Configuration=Release -includeReferencedProjects -o "NuGetRelease"

.nuget\nuget pack Candor.Security.AzureStorageProvider\Candor.Security.AzureStorageProvider.csproj -Build -p Configuration=Release -includeReferencedProjects -o "NuGetRelease"

References

MsBuild Community Tasks
https://github.com/loresoft/msbuildtasks

Parse.Net 4.0 Solution and Project files
http://stackoverflow.com/questions/707107/library-for-parsing-visual-studio-solution-files/4634505#4634505

Creating and Publishing a NuGet package
http://docs.nuget.org/docs/creating-packages/creating-and-publishing-a-package

Simple MSBuild Configuration: Updating Assemblies with a Version Number
http://weblogs.asp.net/srkirkland/archive/2010/12/07/simple-msbuild-configuration-updating-assemblies-with-a-version-number.aspx

NuGet Support for project references:
http://docs.nuget.org/docs/release-notes/nuget-2.5#Improved_project_reference_support_for_NuGet.exe_Pack

About the Author

Michael Lang

Co-Founder and CTO of Watchdog Creative, business development, technology vision, and more since 2013, Developer, and Mentor since 1999. See the about page for more details.

2 Comments