Roman Kuzmin edited this page Mar 21, 2018 · 21 revisions

Why are these build scripts? Why not just use normal scripts with functions instead of scripts with tasks? This is a reasonable question and in many cases scripts with functions is the right choice. But in some scenarios scripts with tasks are easier to compose and use due to special conventions and features.

Typical build script scenarios

There are some files in a directory and some operations on these files, tasks, often related. Such a directory is often called "project" or "workspace". The tasks should be automated and easily invoked individually or as combinations.

This is a typical scenario where a build script with tasks may have advantages over a normal script with functions, especially if its location is the project directory.

The current location is known

The current location is always set to the directory of a build script being invoked, often the project directory. This is done before invoking any code: the script itself, tasks, conditions, inputs, outputs, blocks, etc.

Due to this convention, tasks dealing with project files simply access them by relative paths. External scripts in the same project are also easily invoked by relative paths. Tasks may change the current location and do not have to care of restoring it.

This is not the case for normal scripts. The location of files has to be set current manually or specified by a parameter or calculated. Invocation of external scripts by relative paths also needs some coding. If a function changes the current location then it may have to restore it for others.

Tasks may have relations

Tasks may refer to other tasks to be invoked as well. Task relations allow easy composition of complex task trees. See Show Build Graph for examples.

A build script caller does not have to know all the relations. It calls the tasks that should be done. Related tasks are discovered by the engine and invoked in the proper order according to defined relations.

This is not the case for a set of functions in a normal script. All required functions have to be known to a caller and invoked explicitly in a correct order. This may be easy with a few functions but it is difficult and error prone in complex scenarios with many related operations.

Of course, some functions may call other functions themselves, relations are also possible, and a caller does not have to specify functions to be invoked anyway. But...

Tasks are invoked once

A task may be referenced many times by other tasks in the task trees being built. But as soon as it is invoked, it is never invoked again in the same build.

This is not the case with functions which call other functions. A function may be called many times. As a result, without extra care it may do the same job more than once.

Useful sanity checks

A build script is checked by the engine for missing referenced tasks and cyclic references. If there is any then the build stops before invocation of tasks and the problem is explained.

This is not the case for a normal script with functions. Problems like missing functions or cyclic references are discovered somewhere in the middle.

Default error action is Stop

The default PowerShell error action is Continue, so that non terminating errors do not stop processing. This may be useful in interactive mode when errors are analysed before typing next commands.

But in scripts Continue is too dangerous. It is too easy to miss errors that should stop a script for analysis. That is why the build engine sets the default error action to Stop, for safety and certainty.

Thus, a build script knows that its default error action is always Stop. It may change it or better keep it and specify relaxed error actions for some commands.

This is not the case for a normal script. The current default error action is unknown, it depends on various factors, and very likely it is the default not safe Continue. Thus, normal scripts should take care of this themselves.

Handy features and tools

Invocation of build scripts comes with simple and yet useful logging, with colors in a console, task duration measurement, processing of failures with shown error messages and their task stacks. Warnings are collected and shown separately after a build.

Incremental tasks provide effective ways to process files with out of date output and skip files with up to date output.

The build engine provides a few helper commands. Build scripts may use them in order to avoid some tedious coding.

  • use helps to create aliases to often used external tools.
  • exec invokes an application and checks for its exit code.
  • assert tests a condition and fails if it is not true.
  • equals verifies that two specified objects are equal.
  • remove removes the specified temporary build items.
  • property gets a session or environment variable.
  • requires checks for the required build assets.

Active documentation

Build script tasks naturally describe what actions are supposed to be performed for a project, even if they are not documented explicitly. Supportive tools get this information in various ways.

  • Invoke-Build ? shows available tasks with jobs and optional synopses from comments.
  • Invoke-Build ... -WhatIf gets more detailed preview of tasks and jobs to be invoked.
  • Show-BuildDgml and Show-BuildGraph represent task relation graphs visually.
  • Show-BuildTree shows task trees as text.