[Estimated Reading Time: 6 minutes]

After the previous post we should have ourselves a build agent with everything we need to perform command line builds of Delphi code. But let’s make sure!

The first thing we need is some code, so we’ll need a git repository in Azure DevOps. Before we can get to that though, we need a Project. In Azure DevOps, a project is a container for any number of repositories you may wish but also backlogs for managing work items relating to those repositories and much else besides.

Projects may be private or public and which you choose depends on your collaboration model. Private repositories can still be used collaboratively but people need to be invited to join those projects.

But it is not the aim of these posts to get into the details of Azure DevOps (maybe another time), so for our purposes we’ll create a private Hello Delphi project:

Once created (it takes a few seconds) we can navigate to the project and its repositories. Initially there is a single, default repository (named for the project) but it contains no code at this point. The quickest and simplest way to get something into that repo is to add a README.MD and .gitignore directly from the Azure DevOps UI:

Here we do find some awareness of Delphi as a default .gitignore file is available! \o/

Once we’ve initialised the repo we are presented with the contents of that repo (the README.MD and .gitignore we just created) and can now add additional files. Adding folders is a bit trickier (even though it is offered as an option) because git repos do not track empty folders; to create a folder you actually need to create a file and specify a new path involving the new folder. But that’s not a problem right now as our next step is to create a simple Delphi project (i.e. dpr) to exercise our build agent with.

We could of course clone the repo to a machine and work there, pushing our changes to the Azure DevOps repo as we go, but everything we need to do for this exercise we can do directly from with Azure DevOps.

However you choose to do it, we just need two files:

  1. HelloDelphi.dpr – a simple Delphi project which we can compile on our build agent
  2. build.yml – a YAML build pipeline that describes the build job. (This can actually be named whatever we like)

After adding each file you are presented with a file editor to create some initial content for the file before it is committed to the repo.

First, HelloDelphi.dpr:

// File: Hello Delphi / HelloDelphi.dpr


program HelloDelphi;
  WriteLn('Hello Delphi!');

And then build.yml:

# File: build.yml

- master

  name: The Den
  demands: Delphi.7

- powershell: |
    c:\dcc\7\bin\dcc32.exe HelloDelphi.dpr -Uc:\dcc\7\lib -CC
    if (Test-Path -path HelloDelphi.exe) {
  displayName: Build and run tests (Delphi 7)

Full documentation of the YAML build format is beyond the scope of these posts but is available from Microsoft.

NOTE: YAML can be an infuriating format. It relies on whitespace and indentation to convey structure and meaning and errors can be very difficult to spot unless using a YAML aware editor, such as the online one provided by Azure DevOps itself. Even then, an error highlighted in your YAML might be caused by a whitespace problem elsewhere in the file!

For my build step I have chosen a powershell script but I could equally have used a simple cmd.exe based script by using - script in place of - powershell

In addition to the build script itself, there is some additional configuration that tells Azure DevOps how to work with this build:

  • The - trigger identifies that the build is configured to be triggered by changes on the master branch.
  • The - pool identifies that this build is to run in the agent pool called The Den and requires an agent that has the capability Delphi.7.

Remember, the user-defined capabilities I added to my agent identify which specific Delphi versions are available on the machine and the location in which they are to be found. You will note however that in this script, the location of the Delphi 7 compiler is hard-coded.

Unfortunately I haven’t yet found a way to obtain the values of user-defined capabilities in a pipeline that doesn’t involve creating a custom task which is way beyond the scope of these posts.

There is a plug-in in the Visual Studio Marketplace which purports to make these values available via environment variables but I haven’t yet succeeded in getting this to work (it just crashes my pipeline).

I am using Delphi 7 for this initial test simply because it requires the least configuration of all the Delphi versions to get a simple build up and running but you should be able to adapt this to whatever version you need. If you haven’t previously configured command line builds for later versions, be aware that things became significantly more complicated with support for platforms other than Windows, but it’s still not that difficult.

In a later post we’ll see a much more comprehensive approach.

I anticipate that some will suggest I use MS Build but I haven’t yet investigated whether MS Build can be successfully used with side-by-side Delphi installs without full IDE installation. In any event, for this exercise this would be an unnecessary additional complication since at the very least it relies on IDE created configuration files.

Clearly a great deal more work is needed to turn this into something suitable for production use with real-world Delphi applications, but remember that the aim here is to test the basic ability of the agent to build with Delphi. Keeping things simple will help us focus on that without getting bogged down in the details (which will come later).

At this point build.yml is just a file in our repo and isn’t doing anything for us. To turn it into something useful we need to create a pipeline and associate this file with it.

To do this, we go to the Pipelines area of our Hello Delphi project. Since we don’t have any currently we are prompted to create our first one and choose to create a pipeline whose code is stored in Azure Repos Git:

Following the prompts, we select the repo (there’s only the one we were just working with) and can then choose either to start with a Starter Pipeline (i.e. a new file with some very basic content which won’t be of any use to us) or identify an existing YAML file. This latter option is what we need since we just created the file we need (build.yml).

Once we have chosen the file, we are dropped into the pipeline editor (a basic text editor with some YAML and pipeline syntax smarts) where we can make some final tweaks and correct any of those pesky whitespace errors I mentioned. But we should be good to go and can just run the pipeline.

Once it completes (hopefully) successfully, you can then drill into the job details and should see something like this:

Each of the steps in the job can be expanded to see more detail. Most of these steps are boiler-plate operations performed by Azure DevOps to send work to the agent, including checking out the repo to provide the necessary files for the agent to work on.

The step we’re most interested in is the middle one: Build and run tests (Delphi 7). This was the displayName we specified for our build step so expanding this we should see the output from the powershell step in our build.yml:

And there we have it.

We can see that our simple script to run the compiler was indeed successful and that following compilation we identified the resulting exe file and ran it to obtain the Hello Delphi! output to the console.

I just said that you can drill into this detail once the job is completed, but in fact you can also view the output of a build as it happens, with logs sent in real-time even from the agent running on your machine to be viewed via the Azure DevOps UI live!

So, I’ve established that my build agent is able to successfully compile my Delphi code, even though Azure DevOps knows nothing about Delphi (other than in providing a default .gitignore for my repos).

Next time we’ll skip ahead somewhat with a more comprehensive and sophisticated build script. We’ll also do something more useful after a successful build: Executing some tests and capturing the results of those tests to obtain code quality metrics from Azure DevOps.