Damian Hickey

Mostly software and .NET related. Mostly.

A NuGet (for tools) / OpenWrap (for libs) hybrid Visual Studio solution template

I've created a solution template as a that may form the basis of reference solution to be used in my company. It's available to view here

Requirements & Design decisions:

  1. To be used in a corp environment.
  2. No packages / wraps / dependencies / tools are committed to version control, except the bare minimum to bootstrap the build and/or restore dependencies.
  3. Packages / wraps / dependencies / tools may come from a variety of sources - nuget galley, local nuget repo, local openwrap repo.
  4. Anyone should be able to download the source and single click build the solution with no additional effort, friction or setup. (i.e. just run Build.ps1 )
  5. Easily managed via CI without addition setup and minimal coupling (i.e. .\Build.ps1 -buildNumber 2 )
  6. Sticking with released version of tools.
  7. I am using NuGet as my 'tools' manager. OpenWrap does not appear to be suitable for this purpose.
  8. OpenWrap seems to want to be responsible for compiling, running tests and creating the package. I already have psake for that. In my mind, creating a packages is a post-build step. Anyway, build: none to the rescue. 
  9. A psake build task sets the version number (using a build number if supplied) and keeps SharedAssemblyInfo.cs and the version file in sync.
  10. We use code contracts and the contract assemblies must be shipped in the wrap.

Some of the things I don't like:

  1. There is a bit too much going on in the root of the project, but at least Build.ps1 is at the top.
  2. Would rather not have to commit warps\openwrap-1.0.0.53270092.wrap to  support the bootstrap.
  3. ... and I'd like to add wraps/* to .hgignore.
  4. Can't isolate XUnit to only the DH.SolutionTemplate.Tests project (I know something is in the pipeline for this).
  5. Explicitly defining what goes into the resulting wrap may be a place where mistakes can easily happen. Not sure how to mitigate that.
  6. Having to use ;-shellInstall none; everywhere. I think o.exe default behaviour should be 'do nothing' and installation is an explicit step. I don't know enough of OpenWraps's architecture or intention to hold that belief firmly though :)
  7. In RestoreDependencies.ps1 I am deliberately adding the NuGet gallery remote source. This will add it permanently to the users remote list which I'd prefer not to affect. (I know there is something in OpenWrap's pipeline for this).

So, if there are any places this could be improved, let me know.

Using OpenWrap without having to install it

Well shortly after my last post on getting OpenWrap to work with TeamCity I was told that one can just include o.exe with your solution and use the switch

 

o -shellInstall none [rest of args]

 

instead.

This made me face-palm :)

In my defence, this switch is not shown via 'o', or 'o get-help'. Nor is it easily discoverable via the documentation / wiki when following links from openwrap.org. I also thought that because the default action of 'o' is to prompt you to install, I thought one had to.

And with that, I am of the opinion that 'o''s default action shouldn't prompt to install, and that installing should instead be an explicit switch ( '-install' ).

Getting TeamCity Build Agents and OpenWrap to work together

Just had some fun trying to get OpenWrap (1.0.0.53270093) working on TeamCity (6.0), and to be honest, I'm not entirely sure that I am doing it right. My build tool of choice is psake, and I am calling OpenWrap from within one of my build tasks.

Anyway, this is what I had to do and feedback is, of course, appreciated :)

1. Login to your build server with the same credentials your agents run under.

OpenWrap likes to install itself into the user specific application data folder, c:\Users\[User]\AppData\Local\openwrap, so you will need to login to your build server with the same credentials the TeamCity build agents run under. So they could access network resources, my agents run with domain credentials so this wasn't a problem.

2. Install OpenWrap with the 'i' option, "install the shell and make it available on the path?".

This will install OpenWrap in the folder mentioned above. The 'c' option, "use the current executable location and make it available on the path?" threw a DirectoryNotFoundException for me. Choosing 'n', "do nothing?" means that o.exe will continue to prompt you the installation questions.

This is where the fun begins. OpenWrap will add an entry to the user's (as opposed to the system's), PATH environment variable, creating the PATH variable if it didn't already exist. In my case, it didn't. Nothing wrong with this but there seems to a bug with TeamCity whereby, if the user's PATH environment variable is specified, the agent will use that and ignore the system one altogther. The expected behaviour would be to concatenate both.

So when restarting the agent, it's PATH enviroment variable went from this:

to this:

...and that broke a whole bunch of things!

3. Delete the User PATH environment variable.

Control Panel -> System -> Advanced Tab -> Environment Variables -> User variables for [username].

4. Modify buildAgent.properties

Open buildAgent.properties (typically located in C:\TeamCity\buildAgent\conf) in notepad and add the following line:

env.PATH=%env.PATH%;C\:\\Users\\[username]\\AppData\\Local\\openwrap

This is a workaround that adds the OpenWrap path to the system's PATH variable just for the use of the agent.

5. Restart Agent service

The agent's PATH variable should now be fixed:

And your build scripts should be able to call 'o' without problem.

 

Even though the bug is entirely lies with TeamCity, I do think OpenWrap could provide an better mechanism for such deployment scenarios. My system admin would prefer actually prefer MSI.. I don't know enough about OpenWrap's architecture perhaps an install for 'all users' would help?

If you spot anything wrong, or there is a better way, do let me know.

Patching OpenWrap descriptor with a branch name.

Firstly, why would would one want to do this? Well, it would depend on your team's setup. If you like to regularly create branches (for features, versions, spikes or otherwise), you may also like to have those branches built under CI. The problem is that it is not desirable to have the artifacts / packages of builds of these branches conflicting. Especially if you publish these artifacts to a shared location on a network drive.

Since two branches may have the same 'version', the best way (that I can come up with) to differentiate OpenWrap packages is based on the branch name, since that is usually guaranteed to be unique within a VCS.

At this point I am mostly experimenting, so I don't have this in active use, but this is how I am considering doing it. My build script tool of choice is PSake and the VCS is HG, but the concept should work for other setups.

Take an OpenWrap.desc file:

name: DH.Common
depends: Xunit
depends: ...

Add a field to the name and commit this:

name: DH.Common.%BranchName%
depends: Xunit
depends: ...

Add a build task to 'patch' the OpenWrap.desc file. This should be exected before the openwrap task:

task PatchOpenWrapDesc{
   $branchName = hg.exe branch
   Get-Content $rootDir\OpenWrap.desc | `
      Foreach-Object {$_ -replace "%BranchName%", "$branchName"} | `
      Set-Content $rootDir\OpenWrap.desc
}

This will result in a wrap called "DH.Common.branchName.wrap" and will be not clash with wraps created from other branches, by default.

If there is need to designate a branch as a 'release', removing the branch name patching is a dilberate step, which I prefer from an SOP point of view.