I’ve been a big fan of Azure DevOps since the early days when the service was still named Visual Studio Online. I use it for both professional and personal projects, and I regularly recommended it to my clients on consulting projects.
However, no matter how much I evangelize the platform, it is often challenging to convince some Node or Java developers that Azure DevOps will work just as well for their projects as it does for the .NET team. Regardless of how many demos and presentations I give that contradict these assumptions, there are always a handful of nay-sayers in every group that firmly believe that ADO won’t work for them because “it’s a Microsoft tool”.
Philosophical debates aside, I attribute much of the resistance to a lack of understanding on how Azure DevOps has evolved from its Team Foundation Services (TFS) predecessor to become a modern best in class suite of tools that that can support projects of any size, for “any language, on any platform”. The question becomes, how could I unequivocally prove, once and for all that oft repeated mantra?
I toyed with this idea for a bit and then it dawned on me. The proof would not come from building yet another CI/CD demo for SpringBoot microservices deploying to Kubernetes on AWS, it would come from doing something a bit more… whimsical.
And now for something completely different…
Instead of focusing on modern languages or platforms, what if we went back 30 years to see if we could use modern tools and Azure DevOps to develop, build, package, and deploy a program written for an 8-bit computer platform?
As with similar DevOps demos that I’ve done in the past, I wanted to show was a complete end-to-end solution starting with editing the code, checking it in, and taking that all the way to a live environment where we could see the change.
While all this sounded good in theory, the real question was where to start? Obviously this was not a situation where I could scour StackOverflow for prior examples. However, after discussing the idea with a few friends and coworkers (who were all convinced that I had gone completely crazy), the idea took shape.
I decided that the demo program should be written in 8-bit machine language for the Commodore 64, using VS Code as the editor. The source code would be managed in a Git repo on Azure DevOps, and CI/CD pipelines would handle builds, packaging and deployment to Azure. I was also beginning to think my friends were right, I may have finally lost it, but this seemed like an interesting challenge.
Selecting the editor
We’re all familiar with the old saying of “the right tool, for the right job”. In my opinion this applies whether you are doing a home improvement project, working on your car, or writing code.
As a .NET developer, I spend much of my day in Visual Studio 2017. If I need to switch to a JAVA or native Android project, I fire up IntelliJ or Android Studio respectively. So what editor would I use to write Machine Language for a C64?
Believe it or not, there are a few very good modern editors available specifically for writing CBM applications. I played with a few of them, but what I really wanted to use was Visual Studio Code. I use VS Code for a few other development projects and I find it to be highly versatile, it’s a comfortable environment, and the built-in git integration is a “nice to have” that is missing in the other tools.
The downside of this decision would mean that I would sacrifice having syntax highlighting for 6510 assembly language, and I would be stuck starting at black text on a white background. Not exactly consistent with the “right tool” statement I just made.
On a whim, I decided to visit the Visual Studio Marketplace to see if by some chance there already was a VS Code extension available that I could use to solve this problem. I was actually both pleased and surprised to find a handful specifically geared towards Assembly. Unfortunately, none of them were targeted towards the ACME Cross Assembler that I had selected for the project. (More on ACME later)
Undeterred, and not wanting to relive the monochromatic aspects of late 80’s development, I dug into the Microsoft documentation on building a VS Code extension. After a few days of tinkering I joyously released the first version to the Visual Studio Marketplace.
This exercise actually proved much more useful than I originally anticipated.
I never really got the hang of writing assembly back in the day. I tried to learn it a few times, but would usually wind up getting bored or frustrated and then moved on to something else. By creating the plugin, not only did I learn the mechanics of creating a VS Code extension, I also gained a better understanding of the obscure syntax and op codes used in the language.
Armed with a proper editor and a few old programming books from the 80’s, I started coding. In no time at all I had a working program that imported a SID music file and played an 8-bit rendition of “When I’m 64” by the Beatles. (Old Commodore User Group joke, but it seemed appropriate).
Back to the Future
Up until this point, I was compiling and testing the program on my laptop. The source was committed to my git repo, so the next item on the agenda was to create a Continuous Integration build.
One of the things that impresses me with Azure DevOps is its ability to handle almost any CI/CD situation you might encounter. Out of the box the service has a number of pre-installed tasks which permit you to quickly create CI/CD pipelines for most modern development projects with just a few clicks of your mouse.
If what you want to do isn’t part of the base service, the Marketplace will usually turn up a number of different tools that will fit your needs. If that doesn’t work, you can crank out a PowerShell script that can do the job for you.
The obvious challenge for this project was despite the wide range of capabilities that it has to offer, a cross assembler and the disk imaging tasks for the Commodore 64 simply did not exist. Sure, I could stand up a custom agent using a VM in Azure with the tools I needed or I could go the PowerShell route, but what would be the fun in that?
I’m also very opposed to VMs in the cloud, and I wanted to use the Hosted Agents for this exercise, so that left me with only one choice. In order to prove the original point of this exercise, I decided the best way to do it would be to create my own custom extensions to handle these tasks.
The ACME Cross-Assembler Extension
The first extension I needed for my CI pipeline had to compile my assembly code into C64s machine language. For that I needed get a cross assembler installed onto the hosted build agent.
An assembler is a program that converts “human readable” assembly language into the actual binary processor specific machine code. Normally the machine code generated is for the processor used in the machine it is run on.
A cross assembler takes this conversion process a step further by allowing you to generate machine code for a different processor than the one the compiler is run on.
As mentioned earlier, I selected the ACME Cross-Assembler for my project as it is highly recommended for Commodore development. It also supports a wide variety of other 8-bit projects, like the Nintendo Entertainment System or Atari family of machines, that use the 65xx series of processors.
Working from the documentation and examples provided by Microsoft (and a few third-party sources) it took me the better part of a long day to have a working version of the extension written, tested, and published to the Marketplace.
When run, the task automatically downloads the latest version of ACME Cross-Assembler and invokes it with the arguments provided to create an output file for the target platform. One of the advantages in selecting ACME as my cross-assembler is that most of the arguments needed to assemble the program are embedded in the source code itself, which minimized the number of inputs that I needed to define for the extension.
Anyone have a floppy disk lying around that I could borrow?
The next step of the pipeline was to get the program packaged into a Commodore 64 compatible media format, specifically floppy disk. Again, this is not a problem that developers run into on a regular basis.
Lucky for me, there are stand-alone applications available that can do just this.
VICE is the most popular Commodore emulator available. In addition to providing multiple emulators for the different computer models produced by Commodore, it also has a few handy utilities including a virtual disk management utility called c1541. Disk images created with this utility can be used with the emulator, copied to physical media (i.e. low density 5¼” floppy disks), or loaded onto to a micro SD card which can be used with a Commodore SD2IEC drive emulator.
Unlike the ACME task which gets most of its settings from the source file header, the c1541 disk utility relies heavily on the CLI, and there are a number of disk management options available to the user. For this extension I decided to stay focused on just the features needed for the task, but even with that I was presented with a few decisions on how robust I wanted it to be.
Commodore produced three different models of floppy disk drives which varied in how they managed disk capacities based on the formatting and media type. The base model that most people are familiar with could only deal with single sided media and had a capacity of 170kb (yes, kilobytes. Go ask your parents). A later model, the 1571 which was used with the Commodore 128, could handle double sided disks and storage was increased to 340kb. I decided that I wanted my extension to be as versatile as possible, so I added the various disk formats as an argument that could be selected from a pull down in the UI.
As I already had a working example, creating this extension went much faster. In the process, I learned some new things along the way which allowed me to tighten up the code in the ACME Extension as well.
Like the Cross-Assembler extension, the required software is downloaded from the open source project repo and installed on the build agent. The task creates a disk image in the desired format, and then the file from the build task is copied into the image. The resulting file is moved to the Build Artifacts directory where it can be downloaded.
All this work and we’re going to download the file? This is supposed to be a CI/CD tutorial. if we want to prove the point, we need to deploy the program to a machine so that it can be used as part of the demonstration.
The sneakernet is down.
Actual working Commodore 64s and 1541 floppy drives in good condition are a bit of a rare commodity, not to mention very bulky. Besides, they’re incapable of outputting video to modern displays (without special adapters) so that limits their effectiveness for a demo.
The Commodore SX-64 (the first mass-produced “portable” color computer) has a built in monitor, but they are about the same size as my carry on bag and weigh in at 23 pounds. Could you imagine trying to go through airport security with this beast?
The recently released C64 Mini is small enough to travel with, is capable of side loading third party programs from a USB drive, and has an HDMI output. So that could be an option in certain cases. But this still falls short of the goal.
The real issue isn’t portability of hardware, it’s the lack of automation. Having to sneakernet the program from my laptop to the machine on a thumb drive misses the mark for a DevOps presentation.
Sticking with serverless
As I mentioned earlier, VICE is the most popular Commodore emulator currently available. It has been ported to Windows, Linux, Mac OS X, MS-DOS and many other operating systems. However for a cloud deployment, any of these options would require standing up a VM as a host, and I really wanted to stick with with a serverless solution.
This not only works with my compiled program, but I was also able to create a pipeline to deploy and run a program written in Commodore 64 BASIC.
10 PRINT "HELLO WORLD" 20 GOTO 10
Wrapping this up… finally
DevOps for a Commodore 64? Inconceivable! (You keep using that word. I do not think it means what you think it means.)
Less-than-clever jokes aside; the point of this exercise goes beyond the premise of proving that when Azure DevOps claims it can support “any language and any platform“, that they’re not just referring to the Microsoft stack (although to be fair, Commodore did license its version of BASIC for the C64 from Microsoft). Ultimately, it is intended to draw attention away from focusing on technology stacks and silos in order to get people to think beyond the artificial boundaries that they set for themselves and others.
No matter what the supposed rationale, saying something “won’t work here” is often the same as saying “we’ve always done it this way, why should we change”. Both of these phrases show an unwillingness to grow and evolve, to adapt to an ever changing technology landscape, and overcome the challenges that these rapid and aggressive changes often present.
DevOps is not about tools. At the end of the day the tools, no matter who publishes them, are just a means to an end.
In order for DevOps to be successful for an organization, it requires a change in the way we think and an evolution our corporate culture. It means that we need to adopt many new ideas, to rethink what we thought we knew, and to break away from thinking that “it won’t work here” because we haven’t tried it before.
Creating a CI/CD pipeline for a 30 year old computer has no real business value, other than to blatantly illustrate this very point.