It is very common to use the same build script for builds triggered by a Pull Request (PR) and those triggered by continuous integration (CI).
Although these builds are similar, their needs are not exactly the same.
In this article we will look at:
- the differences between PR and CI builds;
- how to use the same YAML script for both while disabling unnecessary steps according to the build type.
The Problem
Steps in a PR build
A build triggered for each Pull Request usually contains these steps:
- choose the .NET Core SDK: specifying the SDK version through global.json ensures the build behaves like it does on your machine;
dotnet restore: restore the NuGet packages;dotnet build: compile the application;dotnet test: run unit and integration tests. Validating every PR helps detect regressions as early as possible.
Steps in a CI build
- choose the .NET Core SDK;
dotnet restore;dotnet build;dotnet publish: create the binaries in a dedicated folder;- publish the artifacts so a release pipeline can use them.
The PR build contains the test step. Running it again during CI is unnecessary when the Pull Request has already validated the same revision.
Why different stages?
The CI build instead contains two additional steps related to publishing artifacts. A PR build does not need to prepare or publish binaries for a release.
Why keep the build time low?
Keeping build and release times low matters for several reasons:
- it directly affects how quickly you can publish;
- fast feedback tells the team whether a PR can be merged without slowing delivery;
- build-agent time is limited, so it should only be spent on useful work 🙂
My solution
A simple solution is to enable or disable build steps according to the trigger by using conditions.
To enable a step for PR builds, add this condition to the step:
condition: and(succeeded(), eq(variables['Build.Reason'], 'PullRequest'))
To activate a step during CI builds I will add the following code to each step:
condition: and(succeeded(), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI'))
Example of full script:
#Lance la build CI lors d'un merge sur develop staging et master
trigger:
- develop
- staging
- master
# Lance la build pr sur les branches suivantes
pr:
branches:
include:
- develop
- staging
- master
# Image de la VM utilisée
pool:
vmImage: 'ubuntu-latest'
variables:
solution: '**/*.sln'
buildPlatform: 'Any CPU'
buildConfiguration: 'Release'
steps:
- task: UseDotNet@2
displayName: use dotnet SDK
inputs:
packageType: 'sdk'
useGlobalJson: true
- task: DotNetCoreCLI@2
displayName: dotnet restore
inputs:
command: 'restore'
- task: DotNetCoreCLI@2
displayName: dotnet build
inputs:
command: 'build'
projects: '**/*.csproj'
arguments: '--configuration $(BuildConfiguration)'
- task: DotNetCoreCLI@2
displayName: dotnet test
condition: and(succeeded(), eq(variables['Build.Reason'], 'PullRequest'))
inputs:
command: 'test'
projects: '**/*Test.csproj'
- task: DotNetCoreCLI@2
displayName: dotnet publish
condition: and(succeeded(), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI'))
inputs:
command: 'publish'
publishWebProjects: true
arguments: '--output $(build.artifactstagingdirectory) --no-build --configuration $(BuildConfiguration)'
- task: PublishBuildArtifacts@1
displayName: publish artefacts
condition: and(succeeded(), in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI'))
inputs:
PathtoPublish: '$(Build.ArtifactStagingDirectory)'
ArtifactName: 'drop'
publishLocation: 'Container'
Happy building 🙂