Tuesday, 22 November 2016

Automated Builds for Visual Studio Project/Solution with custom NuGet packages and Bamboo

Atlassian Bamboo

We've started using Atlassian Bamboo as a build server for our mainly .Net projects. Apart from the fact that it plays nicely with Jira and Bitbucket and it seems to be very nice to use, it also supports a useful feature that it can build every branch that is pushed rather than just the "master". This allows a build for every defect fix which will live in its own branch but without having to set them up each time.

Bamboo Concepts

Anyway, the concepts are a little confusing in Bamboo because it is capable of very complex builds and workflows but you basically create a:

  1. Project - This is basically what you expect and will likely map onto a single solution in source control. It might however be a "super" project which would integrate several solutions into a single product.
  2. Plan - Each plan is like a "build type" so you might have one for Continuous Integration, one for each type of deployment etc. CI is likely to be automatically triggered and deployments are likely to be manually triggered only.
  3. Stage - Each plan has 1 or more stages. Stages will be run one after the other but are able to share artifacts between them i.e. the output of one stage can be fed to the next.
  4. Job - Each stage can have 1 or more jobs. Jobs are designed to be run in parallel on multiple agents so if you want something simple, just use a single job. Bamboo licensing is based around jobs so adding multiple jobs can cause you to require more licensing.
  5. Task - Each job runs 1 or more tasks. This is the lowest level entity. Each task will run one after the other but you are allowed to say whether the task always runs on failure of any tasks. This might be useful for calling a cleanup task. These are called "Final tasks".

Tasks for Building .NET

Source Code Checkout

Firstly, you will need a Source Code Checkout task. Note that the default properties for Bamboo will check everything out under Bamboo Home, which is in C:\Users\ by default and which might easily fill your C drive. I changed these paths and restarted Bamboo to make it use another drive for builds and artifacts.

Most of this should be easy to setup but you might need to setup SSH keys etc. for Bamboo to access Git, Subversion etc. I did this from the command line using git-scm but it looks like Bamboo does its own stuff by creating the application link to BitBucket and making Bamboo create a key.

NuGet Package Restore

In .NET, you are likely to need to restore NuGet packages. You shouldn't store these in your code repository because they are a waste of space and are easy to pull down onto the build server on build.

You will need NuGet.exe, which you can download from nuget.org as a single executable, which might as well be copied into C:\Program Files (x86)\NuGet. You will probably already have this directory if you have installed Visual Studio.

Now is the fun part! If you are only using public NuGet packages, then you should be able to skip the next part and go to "Create the Task" in this section.

Custom Feeds

If you are getting packages from any custom feeds, there are various ways for NuGet to find these. Depending on your version of NuGet, the following config files are used when NuGet attempts a package restore:
  1. Project-specific NuGet.Config files located in any folder from the solution folder up to the drive root.
  2. A solution-specific NuGet.Config file located within a .nuget folder in the solution.
  3. The global config file located in %APPDATA%\NuGet\NuGet.Config
  4. Additional machine-wide config files (NuGet 2.6 and later) located in %ProgramData%\NuGet\Config[\{IDE}[\{Version}[\{SKU}\]]]NuGet.Config
  5. (NuGet 2.7 and later) The "defaults" file located at %PROGRAMDATA%\NuGet\NuGetDefaults.config
So what should we use? As with anything, changing the fewest files is the best option. Option 5, above, is a way to share nuget config between separate users by having a potentially network-wide config. As long as security isn't an issue, this file could be pushed by group policy (or some other way) and avoid any additional setup. Option 4 is if you want to lock it to the build machine only, option 3 for the build user only and options 2 and 1 if the settings need to be project specific.

BE CAREFUL that you are not pulling in a config file into the users Roaming profile accidentally. This happened to me and caused a feed to be disabled! You can find out by opening C:\Users\\AppData\Roaming\NuGet and seeing if anything unexpected is there.

Once you've decided the appropriate config file to edit, add your feed endpoint in the normal NuGet format. If you are using VSTS feeds, you can copy the URL from the "package source URL" box in the "Connect to feed" dialog in VSTS. If the feed does not require authentication, it will simply be a key="" and value="" added under packageSources.

If you need to use a VSTS feed, the easiest way to authenticate is to add the CredentialProvider from VSTS, which is designed to bring up Internet Explorer to allow you to authenticate using your VSTS account and allow the provider to download and secure the creds needed to access the package. The instructions for this are linked in the "Connect to feed" dialog and are rubbish!

Download the CredentialProvider.zip link onto the build machine from the "Download NuGet + VSTS Credential Provider" link on the "Connect to feed" dialog in VSTS. Unzip it and copy it into a directory named "CredentialProviders" under C:\Users\\AppData\Local\NuGet. NuGet will look in all sub directories of this directory if you want it in another directory. If you cannot easily log in as the build user onto the build machine, you might also want to copy the credential provider into another users AppData directory for testing. The credentials will be cached per user so you will need to run NuGet as the build user at some point.

You MUST disable Internet Explorer Enhanced Security (also known as "no functionality" mode) if using the VSTS credential provider. NuGet won't use another default browser possibly due to the file type it is opening.

Log into the build machine somewhere that you can test package restore. I ran a Bamboo plan that just did a source code checkout so I had a starting point. Open a command prompt (doesn't need to be admin) and navigate to the directory that has the solution (unless you want to put in full paths) and run "C:\Program Files (x86)\NuGet\NuGet.exe" restore SolutionName.sln. It should realise that it needs to authenticate using the MS credential provider and open that Visual Studio login window for you to enter your VSTS credentials. Once that is done, they will be downloaded and stored (I believe in the TPM of the machine) so it won't need to be done again.

Remember that it will need to work as the build user since Bamboo will not be running the same thing as you (hopefully)!

Create the Task

Create a second task of type "Script" and set it to use cmd.exe as the interpreter, use inline as the script location and paste in something like: "C:\Program Files (x86)\NuGet\nuget.exe" restore "${bamboo.build.working.directory}\MySolution.sln" into "Script Body"

Building .NET

You can theoretically build .NET projects just with MSBuild which is usually installed into the .NET framework directories in c:\windows\Microsoft.NET but actually there are various dependencies that can make this tricky to setup. You will also need to install Visual Studio Build Tools to ensure you get a batch file that is used by Bamboo.

By FAR the easiest way to do this is to install Visual Studio Community edition for free and ensure you also install Visual C++, which installs the required batch file.

Add a new task of type Visual Studio (or you can try and get an MSBuild task working). Add a new executable if required and note it wants to the path to devenv.exe only, not the file name! Options needs something, e.g. /build Debug and the platform is likely to be amd64.

This should now be enough to manually run the plan and see any errors that are produced by the build in the various logs. A common problem is that the build server rarely has as many libraries installed as the development machine so it will potentially cause dependency errors but that is for another day!

Post a Comment