diff --git a/Tasks/Bootstrap/.test.ps1 b/Tasks/Bootstrap/.test.ps1 new file mode 100644 index 0000000..e606364 --- /dev/null +++ b/Tasks/Bootstrap/.test.ps1 @@ -0,0 +1,10 @@ + +task Direct { + ($r = .\Project.build.ps1 Build -Configuration Release) + assert ($r -contains 'Building Release') +} + +task Engine { + ($r = Invoke-Build Build -Configuration Release) + assert ($r -contains 'Building Release') +} diff --git a/Tasks/Bootstrap/Project.build.ps1 b/Tasks/Bootstrap/Project.build.ps1 new file mode 100644 index 0000000..1a63863 --- /dev/null +++ b/Tasks/Bootstrap/Project.build.ps1 @@ -0,0 +1,40 @@ +<# +.Synopsis + Directly invocable build script with Invoke-Build bootstrapping. + +.Example + PS> ./Project.build.ps1 Build + + This command invokes the task Build defined in this script. + If Invoke-Build is not available, its module is installed. + Then Invoke-Build is called. + +.Example + PS> Invoke-Build Build + + This command may be used when Invoke-Build is available. +#> + +param( + [Parameter(Position=0)] + $Tasks + , + [ValidateSet('Debug', 'Release')] + [string]$Configuration = 'Release' +) + +# bootstrap +if (!$MyInvocation.ScriptName.EndsWith('Invoke-Build.ps1')) { + $ErrorActionPreference = 1 + if (!(Get-Command Invoke-Build -ErrorAction 0)) { + Write-Host 'Installing module InvokeBuild...' + Install-Module InvokeBuild -Scope CurrentUser -Force + Import-Module InvokeBuild + } + return Invoke-Build $Tasks $MyInvocation.MyCommand.Path @PSBoundParameters +} + +# Synopsis: Build project. +task Build { + "Building $Configuration" +} diff --git a/Tasks/Bootstrap/README.md b/Tasks/Bootstrap/README.md new file mode 100644 index 0000000..15437bd --- /dev/null +++ b/Tasks/Bootstrap/README.md @@ -0,0 +1,31 @@ +# Build script with Invoke-Build bootstrapping + +In addition to being directly invokable, see [Direct](../Direct), build scripts +may automatically check for the availability of the command `Invoke-Build` and +install its module when needed + +```powershell +param( + [Parameter(Position=0)] + [string[]]$Tasks, + #... other script parameters +) + +# bootstrap +if (!$MyInvocation.ScriptName.EndsWith('Invoke-Build.ps1')) { + $ErrorActionPreference = 1 + if (!(Get-Command Invoke-Build -ErrorAction 0)) { + Write-Host 'Installing module InvokeBuild...' + Install-Module InvokeBuild -Scope CurrentUser -Force + Import-Module InvokeBuild + } + return Invoke-Build $Tasks $MyInvocation.MyCommand.Path @PSBoundParameters +} + +# the usual build script +task ... +``` + +See [Project.build.ps1](Project.build.ps1) for the working example. + +See [Direct](../Direct) for some more details about direct calls. diff --git a/Tasks/Direct/.test.ps1 b/Tasks/Direct/.test.ps1 index 584b298..a3970ed 100644 --- a/Tasks/Direct/.test.ps1 +++ b/Tasks/Direct/.test.ps1 @@ -1,12 +1,12 @@ task Direct { - ($r = .\my.build.ps1 t1, t2 -Param1 bar -Param2 42) + ($r = .\Project.build.ps1 t1, t2 -Param1 bar -Param2 42) assert ($r -contains 'Param1 = bar') assert ($r -contains 'Param2 = 42') } task Engine { - ($r = Invoke-Build t1, t2 my.build.ps1 -Param1 bar -Param2 42) + ($r = Invoke-Build t1, t2 -Param1 bar -Param2 42) assert ($r -contains 'Param1 = bar') assert ($r -contains 'Param2 = 42') } diff --git a/Tasks/Direct/my.build.ps1 b/Tasks/Direct/Project.build.ps1 similarity index 94% rename from Tasks/Direct/my.build.ps1 rename to Tasks/Direct/Project.build.ps1 index fb3e050..9fa8ffc 100644 --- a/Tasks/Direct/my.build.ps1 +++ b/Tasks/Direct/Project.build.ps1 @@ -17,11 +17,11 @@ parameters are usual build script parameters. .Example - > ./my.build.ps1 + > ./Project.build.ps1 Invoke the default task .Example - > ./my.build.ps1 t1, t2 -Param1 bar -Param2 42 + > ./Project.build.ps1 t1, t2 -Param1 bar -Param2 42 Invoke tasks t1 and t2 with some parameters .Example diff --git a/Tasks/Direct/README.md b/Tasks/Direct/README.md index c468cd0..683458b 100644 --- a/Tasks/Direct/README.md +++ b/Tasks/Direct/README.md @@ -2,7 +2,7 @@ Build scripts are normally invoked by the engine `Invoke-Build`, not directly. If this is inconvenient then decorate a script to make it directly invokable. -Add `Tasks` as the first parameter and the command redirecting the call: +Add `Tasks` as the first parameter and the code block redirecting the call: ```powershell param( @@ -39,19 +39,28 @@ directory or subdirectory then you may omit the script: Invoke-Build [] ``` -Note that `Invoke-Build` parameters are not available on direct calls, i.e. you -cannot specify `Safe`, `Summary`, `WhatIf`, etc. When they are needed use the -usual call by `Invoke-Build`. +See [Project.build.ps1](Project.build.ps1) for the working example. -See the script [my.build.ps1](my.build.ps1) for a working example. +## Caveats -## Bootstrap InvokeBuild +Directly invocable build scripts are handy but they have rules and limitations. -Directly invokable scripts may automatically install `InvokeBuild` when needed. +The rules are the extra parameter `$Tasks` and the code block "who calls me". +This code block must be placed immediately after the script parameter block. + +Script parameters cannot use `Invoke-Build` features, e.g. `parameter` in +default parameter value expressions. + +`Invoke-Build` parameters `Safe`, `Summary`, `WhatIf` are not available on +direct calls. + +## Bootstrap + +Directly invokable scripts may automatically install the `InvokeBuild` module. See examples: -- [08-bootstrap/tea.build.ps1](../01-step-by-step-tutorial/08-bootstrap/tea.build.ps1) - straightforward bootstrapping +- [Bootstrap/Project.build.ps1](../Bootstrap/Project.build.ps1) - straightforward bootstrapping - [Paket/Project.build.ps1](../Paket/Project.build.ps1) - some custom bootstrapping ## Notes diff --git a/Tasks/Paket/.config/dotnet-tools.json b/Tasks/Paket/.config/dotnet-tools.json index 045bdaf..b9052bc 100644 --- a/Tasks/Paket/.config/dotnet-tools.json +++ b/Tasks/Paket/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "paket": { - "version": "5.249.2", + "version": "8.0.3", "commands": [ "paket" ] diff --git a/Tasks/Paket/.gitignore b/Tasks/Paket/.gitignore index 55f3870..283fe38 100644 --- a/Tasks/Paket/.gitignore +++ b/Tasks/Paket/.gitignore @@ -1,3 +1,4 @@ +.paket packages paket-files paket.lock diff --git a/Tasks/Paket/Project.build.ps1 b/Tasks/Paket/Project.build.ps1 index bb4e346..cde55eb 100644 --- a/Tasks/Paket/Project.build.ps1 +++ b/Tasks/Paket/Project.build.ps1 @@ -27,24 +27,22 @@ param( # Direct call: ensure packages and call the local Invoke-Build -if ([System.IO.Path]::GetFileName($MyInvocation.ScriptName) -ne 'Invoke-Build.ps1') { - $ErrorActionPreference = 'Stop' +if (!$MyInvocation.ScriptName.EndsWith('Invoke-Build.ps1')) { + $ErrorActionPreference = 1 $ib = "$PSScriptRoot/packages/Invoke-Build/tools/Invoke-Build.ps1" - # get packages if (!(Test-Path -LiteralPath $ib)) { - # restore paket and other local dotnet tools + # restore paket and other tools dotnet tool restore if ($LASTEXITCODE) {throw "tool restore exit code: $LASTEXITCODE"} - # install packages (if you keep paket.lock, use restore instead) + # ensure packages dotnet paket install if ($LASTEXITCODE) {throw "paket install exit code: $LASTEXITCODE"} } # call Invoke-Build - & $ib -Task $Tasks -File $MyInvocation.MyCommand.Path @PSBoundParameters - return + return & $ib $Tasks $MyInvocation.MyCommand.Path @PSBoundParameters } # Normal call for tasks, either by local or global Invoke-Build @@ -57,5 +55,5 @@ task Build { # Synopsis: Remove files. task Clean { Write-Warning 'This sample removes paket.lock' - remove packages, paket-files, paket.lock + remove .paket, packages, paket-files, paket.lock } diff --git a/Tasks/Paket/README.md b/Tasks/Paket/README.md index 4e24911..13d3a4a 100644 --- a/Tasks/Paket/README.md +++ b/Tasks/Paket/README.md @@ -2,7 +2,8 @@ The sample script *Project.build.ps1* shows how to use automatic bootstrapping. The script is designed as directly invokable by PowerShell and it does not -require `Invoke-Build` installed. `Invoke-Build` is restored using `paket`. +require `Invoke-Build` installed. `Invoke-Build` is restored using `paket`, +locally. The `paket` tool is used as one possible way of getting packages. Instead or in addition, we could use `PSDepend`, `NuGet.exe`, `Install-Module`, etc. @@ -35,53 +36,26 @@ Such a script is designed for scenarios like: - Packages restored by `paket` and stored locally. In this sample, it is `Invoke-Build`. - *paket-files* - - Files generated or restored by `paket. + - Files generated or restored by `paket`. These directories are usually added to `.gitignore`. -## How to get PowerShell modules by paket - -Module entries in *paket.dependencies* should normally use PSGallery source. -Module packages should be downloaded to *packages* (`storage: packages`). -The build script should be designed to import modules from *packages*. - -This looks like a ceremony but it has some advantages. This scenario does not -pollute the usual PowerShell module directories and avoids possible module -version issues. - -## How to customize package/module management - -If `paket` and its *paket.dependencies* is not enough, e.g. you want to install -modules by `Install-Module`, then look at the "bootstrapping" block in -*Project.build.ps1* and add required checks and commands. - -For example, just for the `InvokeBuild` module bootstrapping instead of `paket` -we could use this trivial PowerShell code: - -```powershell -if (!(Get-Module InvokeBuild -ListAvailable)) { - Install-Module InvokeBuild - Import-Module InvokeBuild - #... other stuff -} -``` - ## Steps from scratch -To create the dotnet tool manifest *.config/dotnet-tools.json*, invoke: +Create the dotnet tool manifest *.config/dotnet-tools.json*: dotnet new tool-manifest -To install paket and add its record to the manifest, invoke: +Install paket and add to the manifest: dotnet tool install paket -To create the paket file *paket.dependencies*, invoke: +Create the paket file *paket.dependencies*: dotnet paket init -Add Invoke-Build line to *paket.dependencies*: +Add this line to *paket.dependencies*: nuget Invoke-Build storage: packages -Add the sample build script *Project.build.ps1*. +Use the build script like *Project.build.ps1*. diff --git a/Tasks/Paket/paket.dependencies b/Tasks/Paket/paket.dependencies index c81fa67..fb64eb0 100644 --- a/Tasks/Paket/paket.dependencies +++ b/Tasks/Paket/paket.dependencies @@ -1,6 +1,5 @@ source https://api.nuget.org/v3/index.json storage: none -framework: netcoreapp3.1, netstandard2.0, netstandard2.1 nuget Invoke-Build storage: packages diff --git a/Tasks/README.md b/Tasks/README.md index 3f9fb76..de6738c 100644 --- a/Tasks/README.md +++ b/Tasks/README.md @@ -4,6 +4,7 @@ - [01-step-by-step-tutorial](01-step-by-step-tutorial) - From "Hello world" to featured script. - [Attributes](Attributes) How to use custom attributes with task actions. +- [Bootstrap](Bootstrap) How to install the module automatically. - [Confirm](Confirm) How to use `Confirm-Build` to confirm some tasks. - [Direct](Direct) How to make build scripts invokable directly. - [Dynamic](Dynamic) How to use a dynamic script with dynamic tasks.