Migrate C++ Build Environment to x64
We have a C++ legacy application, which is currently being built on 32-bit Windows with Visual Studio 2005. We need to migrate the build to an x64 system with a more recent version of Visual Studio.
We are dealing with an old application, that may still have 32-bit parts or components in it. Although it builds 64-bit binaries, some parts are 32-bit, specifically the installer. Also, we expect some obstacles during migrations, so let's see if we can plan the process a bit first.
Preliminary Project Plan
Our plan can look like this:
- Install clean 64-bit OS with necessary updates and drivers. This gives us a clean system to start with.
- Install the latest version of Visual Studio on it.
- Install the necessary developer tools such as things to work with source control system, Notepad++, 7zip, etc.
- When we have all the tools ready, checkout the files and try to build the application. Some refactoring work may be needed to make it happen, and also to remove no longer needed 32-bit parts.
Note that we are aiming at setting up a BUILD system with only the pieces on it that are needed to do builds, and nothing more.
Install 64-bit Windows 7 OS from CD, drivers, and updates (excluding telemetry). As usual with Windows, it may take quite a while. After trying for a day, I got through to install updates before Service Pack 1, and the Service Pack 1 itself, at which point Windows Updates stopped working for me (would check for updates forever). Turned out that if you try long enough, you may get eventually through with a list of about 200 important and recommended updates.
To avoid telemetry do not install:
To avoid "Get Windows 10" adware, do not install:
Installing Visual Studio
Let's try to keep things simple and install Visual Studio Community 2015, with the only feature selected: Visual C++ under Programming Languages.
Select Visual C++ Feature
Now it is time for a "Hello World!" program, of course. Build it and see it working, so far everything i good.
We'll also need a few additional tools:
- Something that can check out source code files from a source control system.
- A better browser, as IE cannot be trusted.
- A better text editor, perhaps something like Notepad++.
- If we manage to compile things, we'll also need an installer tool, NSIS.
Building the Application
I tried try opening an old solution in new Visual Studio to see if it can migrate things and build the application. It did not work well, as there were many errors.
Perhaps, a better way is to create a new solution in Visual Studio 2015 and migrate projects in there one by one. Before we do it, however, we need to get rid of something that Visual Studio creates for projects: large .sdf files. As explained here
we can let it store in C:\Temp instead of project directories.
So, we go to Tools > Options > Text Editor > C/C++ > Advanced > Fallback Location
In there, we set "Always Use Fallback Location" and "Do Not Warn If Fallback Location Used" to True, "Fallback Location" to C:\Temp.
Now, if we try to do a build of even a small part of the app with Visual Studio 2015, we'll see various issues, mostly compiler warnings because pointer size is different from win32, and compiler is more picky. This introduces another step into the project, which is refactoring of the app in an effort to eliminate such compiler warnings.
Refactoring the Application
As the app is quite complicated and also requires a lot of changes for a smooth compilation on x64 with a Visual Studio that is 10 years newer, the following process may work.
- We'll create a brand new project in Visual Studio 2015, add a source file with the main entry point, try to compile it, and refactor if necessary. Bring changes back to the existing build system, test, and deploy just to make sure that nothing breaks in the process. This gives us source code in the old build system synchronized, so basically we are working with the same source code copy, but it is a little bit more friendly towards Visual Studio 2015 compilation. When the source file compiles, we'll end up with a lot of unresolved externals.
- Address unresolved external errors by adding other source files and libraries, one by one, and doing the necessary refactoring work as we go. As there are many files, it may take a while. At this point we are aiming at compiling one target (there are many in the app).
- When the project finally compiles, compare its set of source files with the original build system, and add the missing files (I have found one).
- Now we can create target types, as the app has a trial and a complete version, which are slightly different. So, we use Visual Studio 2015 Configuration Manager to add additional target types, and rename existing to match our old build system.
- Do not forget to include all necessary resource files, as a compiled and linked binary is not yet a working binary.
- By now we have one piece of the application, which is built with new Visual Studio. We may try to replace that piece, and use the original build system to build an installer with one refactored file in it.
- Well, after some rudimentary testing, the app appears to be working, and it is very tempting to deploy it. We did it for a brief period, and this is where things went wrong, apparently because the new refactored piece was dynamically linked with DLLs not installed on customers computers. We are forced to rollback immediately and write a test plan.
Apparently, rudimentary testing is not enough, we need a more elaborate checklist that may look like below.
We have to obtain OS install media for testing first. Luckily, we still have access to a Windows 7 CD. As for new ones, Windows 8 Enterprise, Windows 8.1 Enterprise, and Windows 10 Enterprise can be downloaded as 90-day evaluation versions from here
. Install them all in bare bones form without any updates. This will gets us an OS as how it comes from the shop. The point is that our app must work on such systems (without .net installed, etc.)
- Clean install with all defaults works.
- All files are in place, with correct version and certificates.
- The app starts, all major features work as expected.
- All commands in app menu function properly.
- App can be configured by user, confirm this is the case by trying a few different settings.
- Automatic start feature works OK after a reboot.
- Expiration mechanism is OK.
- Uninstall works.
- Clean install and all the steps above work.
- Install over running trial works good.
- Install over running trial into a different destination directory is OK.
- Manual update feature works as expected with or without available updates.
- Automatic update feature works as expected with or without available updates.
Although the above checklist is simple, it will help us catch most obvious bugs. We will definitely be able to see a missing DLL problem, among other potential issues.
Modified Project Plan
Now we can adjust our project plan. There many things that may break, so testing per checklist or a test plan is essential. The other problem is the volume of changes. Ideally, we do not want to deploy a new project in its entirety to users in a single step, but rather go in smaller increments. For example, if our app consists of 7 pieces, we may try to update one piece at a time and involve user testing and reporting early. So, our modified plan may be like this:
- Refactor one binary at a time. Do a pre-release testing on all supported operating systems using our checklist in an effort to detect most obvious bugs.
- If things look good after testing, deploy an update to a small number of users. We can do it by allowing automatic update for a limited time (for example: 1 hour), then rollback to the original build and deploy system.
- Monitor user feedback to see if they report anything that we missed in testing. It takes time to collect user feedback, we can use this time while refactoring other parts of the application.
- If problems are found - fix them, otherwise deploy the update again to a bigger portion of users and repeat the process.
Refactoring for x64
We can now move forward carefully, while working on 1 piece at a time and testing.
To complete the project in reasonable time, it now looks like we may need to limit the refactoring effort only to the migration problem at hand. Meaning that we probably should not allocate a lot of effort in attempting to get rid of all x64 compatibility warnings. We will have to do it as another project later. The scope of this project is, therefore, to make compiling, linking, building the installers, and deployment pieces working on x64 system, while at the same time keeping the source code for all pieces in such a shape that the build still works on x86 in case we have to roll back.
With a bit of minor fixing and testing, we can finally build all pieces together. To make installer builds possible, we have to reuse the NSIS Unicode parts from the original build system. Binary copy seems to work. Not an ideal solution probably, but suits our purpose for this project. And to make deployment possible, we also have to install putty
tool, as our deployment script relies on it to copy files.
Although it may seem like an easy project, it is not. Migration is hard, you touch one thing and it brings with itself more things that need fixing, which in turn depend on other things.
What helps is limiting the scope and not trying to address everything at once.
Also, our original planning was too optimistic. Because of insufficient testing, we had to do a product rollback and adjust the plan.
In the end, the project completed successfully after approximately 1-man-week effort (full-time).
If you need something done, feel free to post a project
here. You can also leave a comment
on this project.