So, how does a YAML pipeline for F&O look like? Not so fast, first a brief intro to the YAML format.
YAML structure 101 #
YAML (short for “YAML Ain’t Markup Language”) is a human-readable data serialization format commonly used for configuration files and data exchange between languages.
Unlike formats such as JSON or XML, YAML is designed to be easy to write and understand at a glance.
YAML’s structure is based on indentation rather than brackets or braces, making it clean and intuitive to read. Here’s a quick breakdown of its main elements:
Key–value pairs: The basic building blocks. Keys and values are separated by a colon and a space:
name: Alice
age: 30
Nested data: Indentation (usually two spaces per level) is used to represent hierarchy:
person:
name: Alice
age: 30
Lists: Represented by a dash and a space:
fruits:
- apple
- banana
- mango
Comments: Start with a hash and continue to the end of the line:
# This is a comment
Pipeline structure #
Now that we know a bit about YAML, here’s how a sample CI pipeline looks:
# Sample YML pipeline for X++ builds
# For more information on build pipelines, see
# https://docs.microsoft.com/en-us/dynamics365/fin-ops-core/dev-itpro/dev-tools/hosted-build-automation
# Change the name of the build to a 4-digit version number to be used for model versioning
name: $(Date:yy.MM.dd)$(Rev:.r)
trigger:
branches:
include:
- main
paths:
include:
- Metadata/**
pool:
vmImage: 'windows-latest'
demands:
- msbuild
- visualstudio
# Declare some shorthand for NuGet package names
# Make editing the path for metadata and NuGet extraction folder easier
variables:
App1Package: 'Microsoft.Dynamics.AX.Application1.DevALM.BuildXpp'
App2Package: 'Microsoft.Dynamics.AX.Application2.DevALM.BuildXpp'
AppSuitePackage: 'Microsoft.Dynamics.AX.ApplicationSuite.DevALM.BuildXpp'
PlatPackage: 'Microsoft.Dynamics.AX.Platform.DevALM.BuildXpp'
ToolsPackage: 'Microsoft.Dynamics.AX.Platform.CompilerPackage'
MetadataPath: '$(Build.SourcesDirectory)\Metadata'
NugetConfigsPath: '$(Build.SourcesDirectory)\Tools\Build'
NugetsPath: '$(Pipeline.Workspace)\NuGets'
steps:
# Install NuGet and use -ExcludeVersion option to avoid paths containing version numbers
- task: NuGetCommand@2
displayName: 'NuGet custom install Packages'
inputs:
command: custom
arguments: 'install -Noninteractive $(NugetConfigsPath)\packages.config -ConfigFile $(NugetConfigsPath)\nuget.config -Verbosity Detailed -ExcludeVersion -OutputDirectory "$(NugetsPath)"'
# Build using MSBuild 16 (VS 2019)
# Provide the needed paths, including semi-colon separated list of reference folders
# /p:ReferenceFolder are metadata folders containing other (compiled) X++ packages that are referenced
# /p:ReferencePath are folders containing non-X++ assemblies referenced (aside from one already in the output folder for the package)
- task: VSBuild@1
displayName: 'Build solution **\*.sln'
inputs:
solution: '**/*.sln'
vsVersion: '17.0'
msbuildArgs: '/p:BuildTasksDirectory="$(NugetsPath)\$(ToolsPackage)\DevAlm" /p:MetadataDirectory="$(MetadataPath)" /p:FrameworkDirectory="$(NuGetsPath)\$(ToolsPackage)" /p:ReferenceFolder="$(NuGetsPath)\$(PlatPackage)\ref\net40;$(NuGetsPath)\$(App1Package)\ref\net40;$(NuGetsPath)\$(App2Package)\ref\net40;$(NuGetsPath)\$(AppSuitePackage)\ref\net40;$(MetadataPath);$(Build.BinariesDirectory)" /p:ReferencePath="$(NuGetsPath)\$(ToolsPackage)" /p:OutputDirectory="$(Build.BinariesDirectory)"'
This is based on the available sample in Microsoft’s GitHub project for X++ samples and tools.
Let’s start from the top. First we have the name of the pipeline, and after it a trigger:
# Change the name of the build to a 4-digit version number to be used for model versioning
name: $(Date:yy.MM.dd)$(Rev:.r)
trigger:
branches:
include:
- main
paths:
include:
- Metadata/**
You can see that after the trigger keyword we have elements that are indented. All that’s indented after a keyword is like a subsection of it.
In this case we have branches, and only include the main one. We’ve also got a path, Metadata , to avoid triggering the pipeline each time some commit in the main branch happens.
Then we have the pool:
pool:
vmImage: 'windows-latest'
demands:
- msbuild
- visualstudio
This is similar to what we had on classic pipelines under Agent job, to define which agent version and requisites we have.
Next we have variables:
# Declare some shorthand for NuGet package names
# Make editing the path for metadata and NuGet extraction folder easier
variables:
App1Package: 'Microsoft.Dynamics.AX.Application1.DevALM.BuildXpp'
App2Package: 'Microsoft.Dynamics.AX.Application2.DevALM.BuildXpp'
AppSuitePackage: 'Microsoft.Dynamics.AX.ApplicationSuite.DevALM.BuildXpp'
PlatPackage: 'Microsoft.Dynamics.AX.Platform.DevALM.BuildXpp'
ToolsPackage: 'Microsoft.Dynamics.AX.Platform.CompilerPackage'
MetadataPath: '$(Build.SourcesDirectory)\Metadata'
NugetConfigsPath: '$(Build.SourcesDirectory)\Tools\Build'
NugetsPath: '$(Pipeline.Workspace)\NuGets'
In classic pipelines, we have variables too. Here we define values that are reused later in the pipeline.
And finally, we have steps. And the steps contain tasks, like a classic pipeline again. The tasks are the individual processes that happen during a build. For example, update the model version, install NuGet packages, build code, or create the deployable package.
steps:
# Install NuGet and use -ExcludeVersion option to avoid paths containing version numbers
- task: NuGetCommand@2
displayName: 'NuGet custom install Packages'
inputs:
command: custom
arguments: 'install -Noninteractive $(NugetConfigsPath)\packages.config -ConfigFile $(NugetConfigsPath)\nuget.config -Verbosity Detailed -ExcludeVersion -OutputDirectory "$(NugetsPath)"'
# Build using MSBuild 16 (VS 2019)
# Provide the needed paths, including semi-colon separated list of reference folders
# /p:ReferenceFolder are metadata folders containing other (compiled) X++ packages that are referenced
# /p:ReferencePath are folders containing non-X++ assemblies referenced (aside from one already in the output folder for the package)
- task: VSBuild@1
displayName: 'Build solution **\*.sln'
inputs:
solution: '**/*.sln'
vsVersion: '17.0'
msbuildArgs: '/p:BuildTasksDirectory="$(NugetsPath)\$(ToolsPackage)\DevAlm" /p:MetadataDirectory="$(MetadataPath)" /p:FrameworkDirectory="$(NuGetsPath)\$(ToolsPackage)" /p:ReferenceFolder="$(NuGetsPath)\$(PlatPackage)\ref\net40;$(NuGetsPath)\$(App1Package)\ref\net40;$(NuGetsPath)\$(App2Package)\ref\net40;$(NuGetsPath)\$(AppSuitePackage)\ref\net40;$(MetadataPath);$(Build.BinariesDirectory)" /p:ReferencePath="$(NuGetsPath)\$(ToolsPackage)" /p:OutputDirectory="$(Build.BinariesDirectory)"'
Stages #
Pipelines can also be broken into higher-level sections called stages.
A stage is a logical phase of your pipeline. For example, build and deploy would be two separate stages with different tasks each. Each stage can have its own jobs and steps, and you can control the order they run in.
Why is this useful for us? In Finance & Operations projects, we usually don’t just build X++! We also want to create a deployable package and deploy it to UAT and production.
Stages let you separate those concerns cleanly:
stages:is at the top level of the file, just likename,trigger, andpool.- Each
stagehas one or morejobs. Eachjobhassteps. - We can make later stages depend on earlier stages with
dependsOn, and even gate them withcondition. - We can publish artifacts from one stage and consume them in the next. That’s how you go from “I built code” to “here’s a deployable package you can release”.
In short, with stages we get a mini lifecycle: build it, package it, deploy it, each phase isolated and controlled.
This is the structure you normally want for real F&O CI/CD.