Skip to content

Persistent Builds

Roman Kuzmin edited this page Jun 12, 2019 · 15 revisions

Long running or interactive workflows with expected interruptions can be automated with persistent builds which allow resuming after interruptions.


Starting persistent builds

In order to make a build persistent, i.e. run it with written checkpoints, use the command Build-Checkpoint with a path to the checkpoint file. The file is created before the first task and updated after each succeeded task. Then the file is deleted if the build succeeds, otherwise it is preserved and supposed to be used for resuming the build. The build resumes starting with the failed or interrupted task.

For example, these commands start persistent builds:

# with the default task and script
Build-Checkpoint temp.clixml

# with the specified tasks and script
Build-Checkpoint temp.clixml @{Task = 'Build', 'Clean'; File = '../Project.build.ps1'}

Resuming persistent builds

In order to resume an interrupted build use Build-Checkpoint with the same (existing) checkpoint file and the switch Resume. Primary build parameters (Task, File, script parameters) are not needed because they are restored from the checkpoint file and ignored in the command. Secondary parameters (Safe, Summary, Result) are still relevant and may be changed.

In many cases, especially in interactive scenarios, just add -Resume to the previous command which started the interrupted persistent build.

Example:

Build-Checkpoint temp.clixml -Resume

Preparing build scripts

Scripts which do not set data shared between tasks, like script variables, global variables, etc. are ready for persistent builds unless they deal with something changing between stopping and resuming.

Scripts which deal only with script scope variables set by tasks and used by other tasks can make these variables persistent simply by declaring them as script parameters, even if they are not actually used as parameters. Script parameters are saved and restored automatically.

If the above is not enough then a script should define export/import blocks for maintaining persistence of build states between tasks.

The export block is called before each task. It outputs data exported to a checkpoint file. The import block is called once on resuming. Its single argument is the data imported from a checkpoint file. It is called in the script scope, so that for restoring script variables it does not have to use the prefix script.

For example, a script uses two variables $Version and $Archive which are calculated by the task SetVariables:

# It is a good idea to declare them always, for Set-StrictMode or to hide
# existing in parent scopes and avoid export/import of irrelevant data

$Version = $null
$Archive = $null

task SetVariables {
    $script:Version = ...
    $script:Archive = ...
}

Other tasks reference this task and use these variables assuming they are set. If a persistent build is interrupted after SetVariables then on resuming the variables will not be set for those tasks. Export and import blocks solve this problem:

Set-BuildData Checkpoint.Export {
    $Version
    $Archive
}

Set-BuildData Checkpoint.Import {
    param($data)
    $Version, $Archive = $data
}

As mentioned before, in this particular case it is possible and perhaps better to make $Version and $Archive persistent by declaring them as parameters:

param(
    # true used parameters
    $Platform,
    ...
    # just for persistence
    $Version,
    $Archive
)

This is it, if there is nothing else to persist then custom export and import are not needed, persistence of parameters is performed by the engine.


Caution

  • Think carefully of what the persistent build state is.
  • Some data are not suitable for persistence in clixml files.
  • Changes in stopped build scripts may cause incorrect resuming.
  • Checkpoint files must not be used with different engine versions.
Clone this wiki locally