VSIX Continuous Integration using Cake and AppVeyor

The Cake for Visual Studio extension recently started publishing prerelease builds for the users to grab the latest fixes and features before we publish the next stable release. You can read more about that in this post on the Cake blog. Today, I'll be doing a deep dive into how we continuously build and deploy the Visual Studio extension using Cake, AppVeyor and MyGet.

This post will be covering the continuous integration part of the CI/CD pipeline, with builds using Cake and AppVeyor. Check back tomorrow for a run-down on Continuous Delivery using MyGet.

Automated builds

The first step is to make sure we have repeatable, automated builds for your VS extension. Fortunately, we at the Cake team know just the tool for the job: Cake! I'm going to assume you've seen and/or used Cake here, and instead jump straight into the code.

The rest of the guide features code snippets that are generally simplified and/or adapted versions of the same code we use to build Cake for Visual Studio, which you can find on GitHub.

Getting started

To get started using Cake to build an extension, you will need at least a .cake file and the PowerShell bootstrapper. To install these, you can use a few different options:

  • Visual Studio: If you have Cake for Visual Studio, you can use the item template for a build script, and install the bootstrappers from the Build menu.
  • VS Code: If you have the Cake extension installed, you can use the Command Palette to install a bootstrapper
  • Yeoman: Install the generator-cake package, and you can run yo cake to quickly add a new script or bootstrappers
  • Download: You can always find up-to-date bootstrappers and templates in the cake-build/resources GitHub repo

Building your extension

Building the VSIX package using a Cake script is pretty straightforward. First, restore any NuGet packages:

Task("Restore")  
    .Does(() =>
{
    // Restore all NuGet packages.
    Information("Restoring solution...");
    NuGetRestore("./Cake.VisualStudio.sln");
});

Next, build the solution:

Task("Build")  
.IsDependentOn("Restore")
.Does(() => {
    Information("Building solution...");
    MSBuild(solutionPath, settings =>
        settings.SetPlatformTarget(PlatformTarget.MSIL)
            .SetMSBuildPlatform(MSBuildPlatform.x86)
            .UseToolVersion(MSBuildToolVersion.VS2017)
            .WithProperty("TreatWarningsAsErrors","true")
            .SetVerbosity(Verbosity.Quiet)
            .WithTarget("Build")
            .SetConfiguration(configuration));
});

Note that the extra .SetPlatformTarget(PlatformTarget.MSIL) and .SetMSBuildPlatform(MSBuildPlatform.x86) are needed for Extensibility Tools to work during the build. Change the MSBuildToolVersion to VS2015 for VSIXv2 compatibility.

Finally, we copy the VSIX to somewhere more convenient:

Task("Post-Build")  
.IsDependentOn("Build")
.Does(() => {
    CopyFileToDirectory("./src/bin/Release/Cake.VisualStudio.vsix", "./dist/");
});

Task("Default")  
    .IsDependentOn("Post-Build");

Now, run build.ps1 and you should end up with a VSIX file in your dist/ folder, repeatable and automated. This is the foundation for our next few steps.

AppVeyor publishing

Building your project with AppVeyor is not only a quick and easy CI solution, it's especially useful once you start working with community contributions. Configure AppVeyor to build PRs, then add the "check" to your GitHub repo's settings and you can have every PR automatically blocked until your CI build passes. As a bonus, AppVeyor supports uploading artifacts with every build, perfect for publishing a latest VSIX package from PRs and releases.

I won't go into too much detail on configuring AppVeyor for your project here, since AppVeyor has pretty excellent documentation in their Getting Started guides.

Even better, Cake has integrated AppVeyor support, so we can add VSIX publishing without changing tools!

First, we add a task to our Cake file to publish the VSIX to the AppVeyor build artifacts:

Task("Upload-Artifact")  
    .IsDependentOn("Post-Build")
    .WithCriteria(() => ShouldPublishToAppVeyor)
    .Does(() => 
{
    AppVeyor.UploadArtifact("./dist/Cake.VisualStudio.vsix");
});

Now how you define ShouldPublishToAppVeyor is up to you, but for us, we use the following conditional: AppVeyor.IsRunningOnAppVeyor && AppVeyor.Environment.Repository.Name == "cake-build/cake-vs". This means we're only running in the main repo (no forks), and this task will only run when we're actually running on AppVeyor. Now, we add a new target specifically for AppVeyor builds:

Task("AppVeyor")  
    .IsDependentOn("Upload-Artifact");

Finally, we will need an appveyor.yml file to tell AppVeyor what to do when it builds the project. The important bits are below, but check out the file for cake-vs on GitHub for some useful tweaks.

image: Visual Studio 2017  
# or use Visual Studio 2015 for VSIXv2

# Build script
build_script:  
  - ps: .\build.ps1 -Target "AppVeyor"
  # Make sure the path above is correct

# Branches to build
branches:  
  # Whitelist
  only:
    - develop
    - master
    - /r/.*/
    - /release/.*/
    - /hotfix/.*/

This instructs AppVeyor to call the bootstrapper with the "AppVeyor" target when it builds, and to only update for the branches you can see above.

When commits/merges are pushed to GitHub, it automatically kicks off an AppVeyor build. Once that completes, you can have a look under the ARTIFACTS tab in the build details in AppVeyor, and you should find our uploaded VSIX file.

AppVeyor build artifacts

The Story So Far

So far, we have a simple script for building your Visual Studio extension into a VSIX package without Visual Studio, we're building extension packages in the cloud with AppVeyor, and you can download the package from any build to quickly test out on your local machine. Check back tomorrow and I'll be exploring how we extend this to include MyGet for a complete Continous Delivery pipeline for our Visual Studio extension.

Comments

comments powered by Disqus