-
-
Notifications
You must be signed in to change notification settings - Fork 59
Design Notes
See also:
- DEV Internal Functions - documentation of internal functions
- DEV Public Functions - design notes on public functions
In spite of the name, Invoke-Build is not necessarily for building something. It offers programming with tasks and their relations. Scripts with tasks are often easier to compose and use than traditional scripts with functions, see Concepts.
Invoke-Build is designed to be a general purpose programming tool in the first place. It is also a build automation tool just because typical build workflows are easily described and automated with tasks.
Why is the command Invoke-Build
implemented as a script, i.e. not a function
of a script module or library?
The main reason which overweights all disadvantages is that a script has its
own scope which is referenced using the prefix, e.g. $script:Variable
. As a
result, tasks may maintain their shared state in a very natural way, as script
scope parameters, variables, functions, aliases, and etc.
The script may be called directly, i.e. without loading a module. Auto-loading of modules in PS v3.0+ is not always applicable, e.g. it may be disabled or a module is not in the module path or PS v2.0 is still in use.
There are minor disadvantages. One of them is not the best possible performance on multiple calls. But the cost is comparable with inevitable cost of parsing and running build scripts.
Some internals are exposed to user code. The work around this is use of weird names so that chances of conflicts are small. Also, internals do not pollute the calling scope, they are gone when a build completes.
In PowerShell 5.0 or with PowerShellGet Invoke-Build is available as a module
(Install-Module InvokeBuild
). The module is still based on scripts, it just
provides aliases for invoking them.
Unlike other tasks runners, Invoke-Build does not have an explicit parameter
for task dependencies. Instead, it uses the parameter Jobs
, where jobs are
task references and actions in any required number and order. This covers the
classic dependency scenarios and more complex: after-tasks, multiple actions.
See Task dependency syntax clarity
Invoke-Build provides most of its its DSL commands for scripts as aliases, not functions, because aliases have higher precedence on command name resolution.
Imagine, task
is a function and a PowerShell session already has an alias
task
of Do-Something
defined. Then build scripts would not work properly
because Do-Something
is actually invoked on their task
statements.
Thus, Invoke-Build exposes the function Add-BuildTask
(Verb-PrefixNoun, as
recommended in PowerShell) and its short alias task
as the DSL command for
scripts. In this case, the existing alias task
is not a problem, it is
hidden by the alias of Add-BuildTask
.
Tasks do not have an option like "continue on error". A task does not know
whether or not its failure is fatal for a build. It is a caller of a task
decides, i.e. another task or some Invoke-Build
command. Start a task
reference with "?" in order to allow its failures.
Example. The task Help makes help files. It is referenced by two other tasks:
task Help {<# make help files #>}
task Package ..., Help, {<# make a package #>}
task Test ..., ?Help, {<# invoke tests #>}
The task Package is supposed to fail if Help fails. So Package references
Help normally as "Help"
.
The task Test is not supposed to fail if help files are not created, tests
should be invoked anyway. That is why Test uses a safe reference to Help
as "?Help"
.
Why do some commands copy their parameters to private variables and remove parameter variables after that?
This is done in order to hide the engine presence from a user code as much as possible, as if this code is invoked directly in PowerShell, not by the engine. Not removed parameters of engine commands would be exposed to scripts with some adverse effects.
For example the parameter Result would hide an existing parent scope variable Result which is supposed to be used in a build script.
Or a user task may use its own local variable Result. If by mistake or not it is used uninitialised then the value of not removed parameter Result would be used unintentionally.
Invoke-Build uses internal functions and variables. Even being declared private
some of them are exposed to various code defined in build scripts. In order to
minimize chances of conflicts weird internal names starting with *
are used.
Invoke-Build tries to minimize the number of variables exposed to user code in order to reduce noise on tasks debugging. Variables are often reused and their names become meaningless, so that just some short names are used.
This is true for the engine code, the script Invoke-Build.ps1. The engine is feature complete, mostly bugs free, well documented and covered by tests. The code was optimized for better performance when the time was right.
Users do not have to look at the engine code in order to learn how to use it. The documentation is provided. Help alone is larger than the source code. And the wiki makes the documentation almost exhaustive.
Contributors may have to understand the code. The developer design notes are provided to make this easier, see the top of this page. If something is not clear it will be explained.
- Concepts
- Script Tutorial
- Incremental Tasks
- Partial Incremental Tasks
- How Build Works
- Special Variables
- Build Failures
- Build Analysis
- Parallel Builds
- Persistent Builds
- Portable Build Scripts
- Using for Test Automation
- Debugging Tips
- VSCode Tips
Helpers
- Invoke Task from VSCode
- Generate VSCode Tasks
- Invoke Task from ISE
- Resolve MSBuild
- Show Build Trees
- Show Build Graph
- Argument Completers
- Invoke-Build.template
Appendix