-
Notifications
You must be signed in to change notification settings - Fork 17
/
build.ps1
381 lines (336 loc) · 15 KB
/
build.ps1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
<#
.DESCRIPTION
Bootstrap and build script for PowerShell module pipeline
#>
[CmdletBinding()]
param
(
[Parameter(Position = 0)]
[string[]]$Tasks = '.',
[Parameter()]
[String]
$CodeCoverageThreshold = '',
[Parameter()]
[validateScript(
{ Test-Path -Path $_ }
)]
$BuildConfig,
[Parameter()]
# A Specific folder to build the artefact into.
$OutputDirectory = 'output',
[Parameter()]
# Subdirectory name to build the module (under $OutputDirectory)
$BuiltModuleSubdirectory = '',
# Can be a path (relative to $PSScriptRoot or absolute) to tell Resolve-Dependency & PSDepend where to save the required modules,
# or use CurrentUser, AllUsers to target where to install missing dependencies
# You can override the value for PSDepend in the Build.psd1 build manifest
# This defaults to $OutputDirectory/modules (by default: ./output/modules)
[Parameter()]
$RequiredModulesDirectory = $(Join-Path 'output' 'RequiredModules'),
[Parameter()]
[object[]]
$PesterScript,
# Filter which tags to run when invoking Pester tests
# This is used in the Invoke-Pester.pester.build.ps1 tasks
[Parameter()]
[string[]]
$PesterTag,
# Filter which tags to exclude when invoking Pester tests
# This is used in the Invoke-Pester.pester.build.ps1 tasks
[Parameter()]
[string[]]
$PesterExcludeTag,
# Filter which tags to run when invoking DSC Resource tests
# This is used in the DscResource.Test.build.ps1 tasks
[Parameter()]
[string[]]
$DscTestTag,
# Filter which tags to exclude when invoking DSC Resource tests
# This is used in the DscResource.Test.build.ps1 tasks
[Parameter()]
[string[]]
$DscTestExcludeTag,
[Parameter()]
[Alias('bootstrap')]
[switch]$ResolveDependency,
[Parameter(DontShow)]
[AllowNull()]
$BuildInfo,
[Parameter()]
[switch]
$AutoRestore
)
# The BEGIN block (at the end of this file) handles the Bootstrap of the Environment before Invoke-Build can run the tasks
# if the -ResolveDependency (aka Bootstrap) is specified, the modules are already available, and can be auto loaded
process
{
if ($MyInvocation.ScriptName -notLike '*Invoke-Build.ps1')
{
# Only run the process block through InvokeBuild (Look at the Begin block at the bottom of this script)
return
}
# Execute the Build Process from the .build.ps1 path.
Push-Location -Path $PSScriptRoot -StackName BeforeBuild
try
{
Write-Host -ForeGroundColor magenta "[build] Parsing defined tasks"
# Load Default BuildInfo if not provided as parameter
if (!$PSBoundParameters.ContainsKey('BuildInfo'))
{
try
{
if (Test-Path $BuildConfig)
{
$ConfigFile = (Get-Item -Path $BuildConfig)
Write-Host "[build] Loading Configuration from $ConfigFile"
$BuildInfo = switch -Regex ($ConfigFile.Extension)
{
# Native Support for PSD1
'\.psd1'
{
Import-PowerShellDataFile -Path $BuildConfig
}
# Support for yaml when module PowerShell-Yaml is available
'\.[yaml|yml]'
{
Import-Module -ErrorAction Stop -Name 'powershell-yaml'
ConvertFrom-Yaml -Yaml (Get-Content -Raw $ConfigFile)
}
# Native Support for JSON and JSONC (by Removing comments)
'\.[json|jsonc]'
{
$JSONC = (Get-Content -Raw -Path $ConfigFile)
$JSON = $JSONC -replace '(?m)\s*//.*?$' -replace '(?ms)/\*.*?\*/'
# This should probably be converted to hashtable for splatting
$JSON | ConvertFrom-Json
}
default
{
Write-Error "Extension '$_' not supported. using @{}"
@{ }
}
}
}
else
{
Write-Host -Object "Configuration file $BuildConfig not found" -ForegroundColor Red
$BuildInfo = @{ }
}
}
catch
{
Write-Host -Object "Error loading Config $ConfigFile.`r`n Are you missing dependencies?" -ForegroundColor Yellow
Write-Host -Object "Make sure you run './build.ps1 -ResolveDependency -tasks noop' to restore the Required modules the first time" -ForegroundColor Yellow
$BuildInfo = @{ }
Write-Error $_.Exception.Message
}
}
# If the Invoke-Build Task Header is specified in the Build Info, set it
if ($BuildInfo.TaskHeader)
{
Set-BuildHeader ([scriptblock]::Create($BuildInfo.TaskHeader))
}
# Import Tasks from modules via their exported aliases when defined in BUild Manifest
# https://github.com/nightroman/Invoke-Build/tree/master/Tasks/Import#example-2-import-from-a-module-with-tasks
if ($BuildInfo.containsKey('ModuleBuildTasks'))
{
foreach ($Module in $BuildInfo['ModuleBuildTasks'].Keys)
{
try
{
Write-Host -ForegroundColor DarkGray -Verbose "Importing tasks from module $Module"
$LoadedModule = Import-Module $Module -PassThru -ErrorAction Stop
foreach ($TaskToExport in $BuildInfo['ModuleBuildTasks'].($Module))
{
$LoadedModule.ExportedAliases.GetEnumerator().Where{
# using -like to support wildcard
Write-Host -ForegroundColor DarkGray "`t Loading $($_.Key)..."
$_.Key -like $TaskToExport
}.ForEach{
# Dot sourcing the Tasks via their exported aliases
. (Get-Alias $_.Key)
}
}
}
catch
{
Write-Host -ForegroundColor Red -Object "Could not load tasks for module $Module."
Write-Error $_
}
}
}
# Loading Build Tasks defined in the .build/ folder (will override the ones imported above if same task name)
Get-ChildItem -Path ".build/" -Recurse -Include *.ps1 -ErrorAction Ignore | ForEach-Object {
"Importing file $($_.BaseName)" | Write-Verbose
. $_.FullName
}
# Synopsis: Empty task, useful to test the bootstrap process
task noop { }
# Define default task sequence ("."), can be overridden in the $BuildInfo
task . {
Write-Build Yellow "No sequence currently defined for the default task"
}
# Load Invoke-Build task sequences/workflows from $BuildInfo
Write-Host -ForegroundColor DarkGray "Adding Workflow from configuration:"
foreach ($Workflow in $BuildInfo.BuildWorkflow.keys)
{
Write-Verbose "Creating Build Workflow '$Workflow' with tasks $($BuildInfo.BuildWorkflow.($Workflow) -join ', ')"
$WorkflowItem = $BuildInfo.BuildWorkflow.($Workflow)
if ($WorkflowItem.Trim() -match '^\{(?<sb>[\w\W]*)\}$')
{
$WorkflowItem = [ScriptBlock]::Create($Matches['sb'])
}
Write-Host -ForegroundColor DarkGray " +-> $Workflow"
task $Workflow $WorkflowItem
}
Write-Host -ForeGroundColor magenta "[build] Executing requested workflow: $($Tasks -join ', ')"
}
finally
{
Pop-Location -StackName BeforeBuild
}
}
Begin
{
# Find build config if not specified
if (-not $BuildConfig) {
$config = Get-ChildItem -Path "$PSScriptRoot\*" -Include 'build.y*ml', 'build.psd1', 'build.json*' -ErrorAction:Ignore
if (-not $config -or ($config -is [array] -and $config.Length -le 0)) {
throw "No build configuration found. Specify path via -BuildConfig"
}
elseif ($config -is [array]) {
if ($config.Length -gt 1) {
throw "More than one build configuration found. Specify which one to use via -BuildConfig"
}
$BuildConfig = $config[0]
}
else {
$BuildConfig = $config
}
}
# Bootstrapping the environment before using Invoke-Build as task runner
if ($MyInvocation.ScriptName -notLike '*Invoke-Build.ps1')
{
Write-Host -foregroundColor Green "[pre-build] Starting Build Init"
Push-Location $PSScriptRoot -StackName BuildModule
}
if ($RequiredModulesDirectory -in @('CurrentUser', 'AllUsers'))
{
# Installing modules instead of saving them
Write-Host -foregroundColor Green "[pre-build] Required Modules will be installed for $RequiredModulesDirectory, not saved."
# Tell Resolve-Dependency to use provided scope as the -PSDependTarget if not overridden in Build.psd1
$PSDependTarget = $RequiredModulesDirectory
}
else
{
if (-Not (Split-Path -IsAbsolute -Path $OutputDirectory))
{
$OutputDirectory = Join-Path -Path $PSScriptRoot -ChildPath $OutputDirectory
}
# Resolving the absolute path to save the required modules to
if (-Not (Split-Path -IsAbsolute -Path $RequiredModulesDirectory))
{
$RequiredModulesDirectory = Join-Path -Path $PSScriptRoot -ChildPath $RequiredModulesDirectory
}
# Create the output/modules folder if not exists, or resolve the Absolute path otherwise
if (Resolve-Path $RequiredModulesDirectory -ErrorAction SilentlyContinue)
{
Write-Debug "[pre-build] Required Modules path already exist at $RequiredModulesDirectory"
$RequiredModulesPath = Convert-Path $RequiredModulesDirectory
}
else
{
Write-Host -foregroundColor Green "[pre-build] Creating required modules directory $RequiredModulesDirectory."
$RequiredModulesPath = (New-Item -ItemType Directory -Force -Path $RequiredModulesDirectory).FullName
}
# Prepending $RequiredModulesPath folder to PSModulePath to resolve from this folder FIRST
if ($RequiredModulesDirectory -notIn @('CurrentUser', 'AllUsers') -and
(($Env:PSModulePath -split [io.path]::PathSeparator) -notContains $RequiredModulesDirectory))
{
Write-Host -foregroundColor Green "[pre-build] Prepending '$RequiredModulesDirectory' folder to PSModulePath"
$Env:PSModulePath = $RequiredModulesDirectory + [io.path]::PathSeparator + $Env:PSModulePath
}
# Checking if the user should -ResolveDependency
if ((!(Get-Module -ListAvailable powershell-yaml) -or !(Get-Module -ListAvailable InvokeBuild) -or !(Get-Module -ListAvailable PSDepend)) -and !$ResolveDependency)
{
if ($AutoRestore -or !$PSBoundParameters.ContainsKey('Tasks') -or $Tasks -contains 'build')
{
Write-Host -ForegroundColor Yellow "[pre-build] Dependency missing, running './build.ps1 -ResolveDependency -Tasks noop' for you `r`n"
$ResolveDependency = $true
}
else
{
Write-Warning "Some required Modules are missing, make sure you first run with the '-ResolveDependency' parameter."
Write-Warning "Running 'build.ps1 -ResolveDependency -Tasks noop' will pull required modules without running the build task."
}
}
if ($BuiltModuleSubdirectory)
{
if (-Not (Split-Path -IsAbsolute $BuiltModuleSubdirectory))
{
$BuildModuleOutput = Join-Path $OutputDirectory $BuiltModuleSubdirectory
}
else
{
$BuildModuleOutput = $BuiltModuleSubdirectory
}
}
else
{
$BuildModuleOutput = $OutputDirectory
}
# Prepending $BuildModuleOutput folder to PSModulePath to resolve built module from this folder
if (($Env:PSModulePath -split [io.path]::PathSeparator) -notContains $BuildModuleOutput)
{
Write-Host -foregroundColor Green "[pre-build] Prepending '$BuildModuleOutput' folder to PSModulePath"
$Env:PSModulePath = $BuildModuleOutput + [io.path]::PathSeparator + $Env:PSModulePath
}
# Tell Resolve-Dependency to use $RequiredModulesPath as -PSDependTarget if not overridden in Build.psd1
$PSDependTarget = $RequiredModulesPath
}
if ($ResolveDependency)
{
Write-Host -Object "[pre-build] Resolving dependencies." -foregroundColor Green
$ResolveDependencyParams = @{ }
# If BuildConfig is a Yaml file, bootstrap powershell-yaml via ResolveDependency
if ($BuildConfig -match '\.[yaml|yml]$')
{
$ResolveDependencyParams.add('WithYaml', $True)
}
$ResolveDependencyAvailableParams = (Get-Command -Name '.\Resolve-Dependency.ps1').parameters.keys
foreach ($CmdParameter in $ResolveDependencyAvailableParams)
{
# The parameter has been explicitly used for calling the .build.ps1
if ($MyInvocation.BoundParameters.ContainsKey($CmdParameter))
{
$ParamValue = $MyInvocation.BoundParameters.ContainsKey($CmdParameter)
Write-Debug " adding $CmdParameter :: $ParamValue [from user-provided parameters to Build.ps1]"
$ResolveDependencyParams.Add($CmdParameter, $ParamValue)
}
# Use defaults parameter value from Build.ps1, if any
else
{
if ($ParamValue = Get-Variable -Name $CmdParameter -ValueOnly -ErrorAction Ignore)
{
Write-Debug " adding $CmdParameter :: $ParamValue [from default Build.ps1 variable]"
$ResolveDependencyParams.add($CmdParameter, $ParamValue)
}
}
}
Write-Host -foregroundColor Green "[pre-build] Starting bootstrap process."
.\Resolve-Dependency.ps1 @ResolveDependencyParams
}
if ($MyInvocation.ScriptName -notLike '*Invoke-Build.ps1')
{
Write-Verbose "Bootstrap completed. Handing back to InvokeBuild."
if ($PSBoundParameters.ContainsKey('ResolveDependency'))
{
Write-Verbose "Dependency already resolved. Removing task"
$null = $PSBoundParameters.Remove('ResolveDependency')
}
Write-Host -foregroundColor Green "[build] Starting build with InvokeBuild."
Invoke-Build @PSBoundParameters -Task $Tasks -File $MyInvocation.MyCommand.Path
Pop-Location -StackName BuildModule
return
}
}