Some months ago I started a long and slow journey to migrate the build process of my MZ-Tools extension from a custom .NET-based builder that ran on my development computer to Visual Studio Team Services, leveraging its Build management and Release management capabilities. My goals are to learn those capabilities and, well, to use them as if I were a team. I haven’t reached yet that destination but I have made significant progress and I am quite close now. In the process I have realized somewhat ashamed that I wasn’t following the best practices in a lot of places. I say “somewhat” because there are some mitigating circumstances:
- Being a solo developer it is too easy to arrange things in such a way that only works on your development machine. You don’t have a team to warn you that it doesn’t work outside your development machine.
- Even if you change your development machine to a new one from time to time, or use two development computers, you use the same username and tend to install and configure the software in the same way.
- My Visual Studio solution and some projects were born in the year 2002, with the first Visual Studio .NET. At that time Team Foundation Server, NuGet, MSBuild, etc. didn’t even exist on paper.
- I have all the Visual Studio versions and VS SDKs from 2005 to 2017 installed on my development machine. That causes that you don’t think carefully where a DLL is referenced from.
- I have also all the .NET Frameworks and SDKs from .NET Framework 2.0 installed on my development machine. Another source for undocumented hidden dependencies.
- My Visual Studio solution has become quite complex over the years with several projects and technologies:
- One project that uses .NET Framework 2.0 and C# for the core plug-in, that is reused at binary level for many Microsoft’s IDEs.
- Four projects with host adapters for Visual Studio (2005, 2008, 2010) as add-in, VBA (Microsoft Office 2000 or higher, even on Windows XP), VB 6.0 and VB 5.0, using .NET Framework 2.0, C# and 3rd party tool for the setups.
- One project with a host adapter for Visual Studio (2012, 2013, 2015 and 2017) as package, using .NET Framework 4.5, C#, and VSIX.
- Four projects with COM Shims for VBA (32-bit and 64-bit), VB 6.0 and VB 5.0 using Visual C++, ATL, Windows 8.1 SDK and the .NET CLR loader APIs.
- One project for a portable version for VBA, using .NET Framework 2.0 and C#.
- Help file and online help generated by a 3rd party tool.
- Two projects with integration tests.
- Obfuscation performed by a 3rd party tool, that requires delay signing.
Microsoft tends to create a new VSIX with each new Visual Studio release but most of us want to create a single package and single VSIX for as many Visual Studio versions as possible, using always the latest Visual Studio version for development (Visual Studio 2017 at the time of this writing). If this is your case, even if your solution is not as complex as mine, ask yourself these questions:
- Would your solution build on a computer with only Visual Studio 2017 installed? Or are you referencing inadvertently DLLs that exist on your machine only because you have old Visual Studio versions or VS SDKs installed?
- Do you know the minimal workloads and individual components of the new Visual Studio 2017 setup that your solution requires to build?
- A challenging one that will require a new post: would it build on a build server with only the Build Tools 2017 installed but without Visual Studio 2017 installed? The Build Tools 2017 “allow you to build native and managed MSBuild-based applications without requiring the Visual Studio IDE. There are options to install the Visual C++ compilers and libraries, MFC, ATL, and C++/CLI support“. Blog here, video here, download here. Which individual components or external setups would it require?
- Do you have all the answers to those questions documented?
In the process that I have followed first I envisioned the final result:
- I would use the hosted agent of the Build management of Visual Studio Team Services to provide the following benefits:
- Gated check-ins to prevent code that breaks the build.
- Gated check-ins to prevent code that violates code analysis rules.
- Integration tests. This will require a major effort because I use my own test runner and testing framework instead of Visual Studio Test with the MSTest framework.
- I would use a private agent on the Release server with the Release management of Visual Studio Team Services to provide the following benefits:
- Tracking of releases deployed to the test environment, to the pre-production environment and to the production environment.
- Maybe automated releases.
For these purposes I decided that I would use a new “Build” configuration of the solution for the Build server. In this configuration the obfuscation, help file and online help, setups, etc. are not created. For the Release server I would use the “Release” solution configuration that performs all those additional steps.
For the Build server it is quite easy and I can use the hosted agent of Visual Studio Team Services since I don’t need any 3rd party tools.
For the Release server I cannot use the hosted agent because I need the 3rd party tools that I use to obfuscate, create the help, setups, etc. In the process I have also removed the need for admin rights that my custom builder required previously (the hosted agent of Visual Studio Team Services doesn’t allow admin rights either).
The milestones would be:
- Migrate the custom .NET-based builder to a MSBuild script. This took me weeks but it’s done.
- The solution and projects, when built in “Release” configuration, would auto-increment version numbers and would obfuscate the output assemblies, build the help, setups, etc. with their own MSBuild targets without requiring external steps.
- A master MSBuild script would perform some additional steps before building the solution (such as creating a workspace, getting latest files, restoring NuGet packages, etc.) and some other steps afterwards (such as publishing and archiving binaries).
- Build on a separate workspace on my development machine. Previously I was building on the same workspace used for development. I know, I know, a horrible bad practice.
- Create a separate temporary virtual machine to act as Release server with only Visual Studio 2017 Community edition installed, without previous Visual Studio versions to identify missing dependencies and build with the master MSBuild script.
- Discard the virtual machine of the previous step and create a new virtual machine with the Build Tools 2017 but without Visual Studio 2017, identify and install missing dependencies, build with the master MSBuild script and document everything. I have reached this point!
- Install a private Visual Studio Team Services agent on the virtual machine that acts as Release server and launch the master build script from the Release management section of Visual Studio Team Services.
- Create additional scripts to publish binaries to the pre-production and production environment rather than using manual FTP.
- Repeat everything with the solution for my ASP.NET web site.
I encourage you to follow the same journey if you are a solo developer (and of course if you are a team). At the very least, the exercise of building on a separate build server with only Build Tools 2017 will unhide you hardcoded paths, references that are not provided by source control or supplied as NuGet packages, etc.