Skip to content

Commit 3fe63f8

Browse files
authored
Make build.psd1 optional and fix bugs in 1.5.2
Merge pull request #89 from PoshCode/feature/NoBuildData +semver:feature because build.psd1 is now optional
2 parents b7dcb8a + 0a8a42f commit 3fe63f8

27 files changed

+422
-219
lines changed

.vscode/tasks.json

+5-10
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@
1010
"isDefault": true
1111
},
1212
"type": "shell",
13-
"command": "${workspaceFolder}\\build.ps1",
13+
"command": "${workspaceFolder}/build.ps1",
14+
"options": {
15+
"cwd": "${workspaceFolder}"
16+
},
1417
"presentation": {
1518
"echo": true,
1619
"reveal": "silent",
@@ -27,18 +30,10 @@
2730
"isDefault": true
2831
},
2932
"type": "shell",
33+
"command": "${workspaceFolder}/test.ps1",
3034
"options": {
3135
"cwd": "${workspaceFolder}",
32-
"env": {
33-
"PSModulePath": "${workspaceFolder}\\Output;${env:PSModulePath}"
34-
}
3536
},
36-
"command": "Invoke-Pester",
37-
"args": [
38-
"${workspaceFolder}\\Tests",
39-
"-PesterOption", "@{ IncludeVSCodeMarker = $True }",
40-
"-CodeCoverage", "${workspaceFolder}\\Output\\*.psm1"
41-
],
4237
"presentation": {
4338
"echo": true,
4439
"reveal": "always",

ReadMe.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,15 @@ For best results, you need to organize your module project similarly to how this
9898

9999
1. Create a `source` folder with a `build.psd1` file and your module manifest in it
100100
2. In the `build.psd1` specify the relative **Path** to your module's manifest, e.g. `@{ Path = "ModuleBuilder.psd1" }`
101-
3. In your manifest, make sure the `FunctionsToExport` entry is not commented out. You can leave it empty
101+
3. In your manifest, make sure a few values are not commented out. You can leave them empty, because they'll be overwritten:
102+
- `FunctionsToExport` will be updated with the _file names_ that match the `PublicFilter`
103+
- `AliasesToExport` will be updated with the values from `[Alias()]` attributes on commands
104+
- `Prerelease` and `ReleaseNotes` in the `PSData` hashtable in `PrivateData`
102105

103106
Once you start working on the module, you'll create sub-folders in source, and put script files in them with only **one** function in each file. You should name the files with _the same name_ as the function that's in them -- especially in the public folder, where we use the file name (without the extension) to determine the exported functions.
104107

105108
1. By convention, use folders named "Classes" (and/or "Enum"), "Private", and "Public"
106-
2. By convention, the functions in "Public" will be exported from the module
109+
2. By convention, the functions in "Public" will be exported from the module (you can override the `PublicFilter`)
107110
3. To force classes to be in a certain order, you can prefix their file names with numbers, like `01-User.ps1`
108111

109112
There are a *lot* of conventions in `Build-Module`, expressed as default values for its parameters. These defaults are documented in the help for Build-Module. You can override any parameter to `Build-Module` by passing it, or by adding keys to the `build.psd1` file with your preferences.

Source/Private/GetBuildInfo.ps1

+81-37
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,7 @@ function GetBuildInfo {
22
[CmdletBinding()]
33
param(
44
# The path to the Build Manifest Build.psd1
5-
[Parameter(Mandatory)]
6-
[ValidateScript( {
7-
if ((Test-Path $_) -and (Split-path -Leaf $_) -eq 'build.psd1') {
8-
$true
9-
}
10-
else {
11-
throw "The Module Manifest must point to a valid build.psd1 Data file"
12-
}
13-
})]
5+
[Parameter()][AllowNull()]
146
[string]$BuildManifest,
157

168
# Pass MyInvocation from the Build-Command so we can read parameter values
@@ -19,60 +11,112 @@ function GetBuildInfo {
1911
$BuildCommandInvocation
2012
)
2113

22-
# Read the Module Manifest configuration file for default parameter values
23-
Write-Debug "Load Build Manifest $BuildManifest"
24-
$BuildInfo = Import-Metadata -Path $BuildManifest
14+
$BuildInfo = if ($BuildManifest -and (Test-Path $BuildManifest)) {
15+
if ((Split-path -Leaf $BuildManifest) -eq 'build.psd1') {
16+
# Read the Module Manifest configuration file for default parameter values
17+
Write-Debug "Load Build Manifest $BuildManifest"
18+
Import-Metadata -Path $BuildManifest
19+
} else {
20+
Write-Debug "Use SourcePath $BuildManifest"
21+
@{ SourcePath = $BuildManifest }
22+
}
23+
} else {
24+
@{}
25+
}
26+
2527
$CommonParameters = [System.Management.Automation.Cmdlet]::CommonParameters +
2628
[System.Management.Automation.Cmdlet]::OptionalCommonParameters
2729
$BuildParameters = $BuildCommandInvocation.MyCommand.Parameters
30+
# Make we can always look things up in BoundParameters
31+
$BoundParameters = if ($BuildCommandInvocation.BoundParameters) {
32+
$BuildCommandInvocation.BoundParameters
33+
} else {
34+
@{}
35+
}
2836

2937
# Combine the defaults with parameter values
3038
$ParameterValues = @{}
3139
if ($BuildCommandInvocation) {
3240
foreach ($parameter in $BuildParameters.GetEnumerator().Where({$_.Key -notin $CommonParameters})) {
3341
Write-Debug " Parameter: $($parameter.key)"
3442
$key = $parameter.Key
35-
# set if it doesn't exist, overwrite if the value is bound as a parameter
36-
if (!$BuildInfo.ContainsKey($key) -or ($BuildCommandInvocation.BoundParameters -and $BuildCommandInvocation.BoundParameters.ContainsKey($key))) {
43+
44+
# We want to map the parameter aliases to the parameter name:
45+
foreach ($k in @($parameter.Value.Aliases)) {
46+
if ($null -ne $k -and $BuildInfo.ContainsKey($k)) {
47+
Write-Debug " ... Update BuildInfo[$key] from $k"
48+
$BuildInfo[$key] = $BuildInfo[$k]
49+
$null = $BuildInfo.Remove($k)
50+
}
51+
}
52+
# Bound parameter values > build.psd1 values > default parameters values
53+
if (-not $BuildInfo.ContainsKey($key) -or $BoundParameters.ContainsKey($key)) {
54+
# Reading the current value of the $key variable returns either the bound parameter or the default
3755
if ($null -ne ($value = Get-Variable -Name $key -ValueOnly -ErrorAction Ignore )) {
3856
if ($value -ne ($null -as $parameter.Value.ParameterType)) {
3957
$ParameterValues[$key] = $value
4058
}
4159
}
42-
} else {
43-
Write-Debug " From Manifest: $($BuildInfo.$key)"
60+
if ($BoundParameters.ContainsKey($key)) {
61+
Write-Debug " From Parameter: $($ParameterValues[$key] -join ', ')"
62+
} elseif ($ParameterValues[$key]) {
63+
Write-Debug " From Default: $($ParameterValues[$key] -join ', ')"
64+
}
65+
} elseif ($BuildInfo[$key]) {
66+
Write-Debug " From Manifest: $($BuildInfo[$key] -join ', ')"
4467
}
45-
Write-Debug " From Parameter: $($ParameterValues.$key)"
4668
}
4769
}
70+
# BuildInfo.SourcePath should point to a module manifest
71+
if ($BuildInfo.SourcePath -and $BuildInfo.SourcePath -ne $BuildManifest) {
72+
$ParameterValues["SourcePath"] = $BuildInfo.SourcePath
73+
}
74+
# If SourcePath point to build.psd1, we should clear it
75+
if ($ParameterValues["SourcePath"] -eq $BuildManifest) {
76+
$ParameterValues.Remove("SourcePath")
77+
}
78+
Write-Debug "Finished parsing Build Manifest $BuildManifest"
4879

4980
$BuildInfo = $BuildInfo | Update-Object $ParameterValues
81+
Write-Debug "Using Module Manifest $($BuildInfo.SourcePath)"
5082

51-
# Resolve Build Manifest's parent folder to find the Absolute path
52-
$BuildManifestParent = (Split-Path -Parent $BuildManifest)
53-
54-
# Resolve Module manifest if not defined in Build.psd1
55-
if (-Not $BuildInfo.ModuleManifest) {
56-
$ModuleName = Split-Path -Leaf $BuildManifestParent
83+
$BuildManifestParent = if ($BuildManifest) {
84+
Split-Path -Parent $BuildManifest
85+
} else {
86+
Get-Location -PSProvider FileSystem
87+
}
5788

58-
# If we're in a "well known" source folder, look higher for a name
59-
if ($ModuleName -in 'Source', 'src') {
60-
$ModuleName = Split-Path (Split-Path -Parent $BuildManifestParent) -Leaf
89+
if (-Not $BuildInfo.SourcePath) {
90+
# Find a module manifest (or maybe several)
91+
$ModuleInfo = Get-ChildItem $BuildManifestParent -Recurse -Filter *.psd1 -ErrorAction SilentlyContinue |
92+
ImportModuleManifest -ErrorAction SilentlyContinue
93+
# If we found more than one module info, the only way we have of picking just one is if it matches a folder name
94+
if (@($ModuleInfo).Count -gt 1) {
95+
# Resolve Build Manifest's parent folder to find the Absolute path
96+
$ModuleName = Split-Path -Leaf $BuildManifestParent
97+
# If we're in a "well known" source folder, look higher for a name
98+
if ($ModuleName -in 'Source', 'src') {
99+
$ModuleName = Split-Path (Split-Path -Parent $BuildManifestParent) -Leaf
100+
}
101+
$ModuleInfo = @($ModuleInfo).Where{ $_.Name -eq $ModuleName }
102+
}
103+
if (@($ModuleInfo).Count -eq 1) {
104+
Write-Debug "Updating BuildInfo SourcePath to $($ModuleInfo.Path)"
105+
$BuildInfo = $BuildInfo | Update-Object @{ SourcePath = $ModuleInfo.Path }
106+
}
107+
if (-Not $BuildInfo.SourcePath) {
108+
throw "Can't find a module manifest in $BuildManifestParent"
61109
}
62-
63-
# As the Module Manifest did not specify the Module manifest, we expect the Module manifest in same folder
64-
$ModuleManifest = Join-Path $BuildManifestParent "$ModuleName.psd1"
65-
Write-Debug "Updating BuildInfo path to $ModuleManifest"
66-
$BuildInfo = $BuildInfo | Update-Object @{ModuleManifest = $ModuleManifest }
67110
}
68111

69-
# Make sure the Path is set and points at the actual manifest, relative to Build.psd1 or absolute
70-
if(!(Split-Path -IsAbsolute $BuildInfo.ModuleManifest)) {
71-
$BuildInfo.ModuleManifest = Join-Path $BuildManifestParent $BuildInfo.ModuleManifest
112+
# Make sure the SourcePath is absolute and points at an actual file
113+
if (!(Split-Path -IsAbsolute $BuildInfo.SourcePath) -and $BuildManifestParent) {
114+
$BuildInfo.SourcePath = Join-Path $BuildManifestParent $BuildInfo.SourcePath | Convert-Path
115+
} else {
116+
$BuildInfo.SourcePath = Convert-Path $BuildInfo.SourcePath
72117
}
73-
74-
if (!(Test-Path $BuildInfo.ModuleManifest)) {
75-
throw "Can't find module manifest at $($BuildInfo.ModuleManifest)"
118+
if (!(Test-Path $BuildInfo.SourcePath)) {
119+
throw "Can't find module manifest at the specified SourcePath: $($BuildInfo.SourcePath)"
76120
}
77121

78122
$BuildInfo
+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
function ImportModuleManifest {
2+
[CmdletBinding()]
3+
param(
4+
[Alias("PSPath")]
5+
[Parameter(Mandatory, ValueFromPipelineByPropertyName)]
6+
[string]$Path
7+
)
8+
process {
9+
# Get all the information in the module manifest
10+
$ModuleInfo = Get-Module $Path -ListAvailable -WarningAction SilentlyContinue -ErrorAction SilentlyContinue -ErrorVariable Problems
11+
12+
# Some versions fails silently. If the GUID is empty, we didn't get anything at all
13+
if ($ModuleInfo.Guid -eq [Guid]::Empty) {
14+
Write-Error "Cannot parse '$Path' as a module manifest, try Test-ModuleManifest for details"
15+
return
16+
}
17+
18+
# Some versions show errors are when the psm1 doesn't exist (yet), but we don't care
19+
$ErrorsWeIgnore = "^" + (@(
20+
"Modules_InvalidRequiredModulesinModuleManifest"
21+
"Modules_InvalidRootModuleInModuleManifest"
22+
) -join "|^")
23+
24+
# If there are any OTHER problems we'll fail
25+
if ($Problems = $Problems.Where({ $_.FullyQualifiedErrorId -notmatch $ErrorsWeIgnore })) {
26+
foreach ($problem in $Problems) {
27+
Write-Error $problem
28+
}
29+
# Short circuit - don't output the ModuleInfo if there were errors
30+
return
31+
}
32+
33+
# Workaround the fact that Get-Module returns the DefaultCommandPrefix as Prefix
34+
Update-Object -InputObject $ModuleInfo -UpdateObject @{ DefaultCommandPrefix = $ModuleInfo.Prefix; Prefix = "" }
35+
}
36+
}

Source/Private/InitializeBuild.ps1

+10-21
Original file line numberDiff line numberDiff line change
@@ -26,29 +26,18 @@ function InitializeBuild {
2626
# GetBuildInfo reads the parameter values from the Build-Module command and combines them with the Manifest values
2727
$BuildManifest = ResolveBuildManifest $SourcePath
2828

29-
Write-Debug "BuildCommand: $($BuildCommandInvocation.MyCommand | Out-String)"
29+
Write-Debug "BuildCommand: $(
30+
@(
31+
@($BuildCommandInvocation.MyCommand.Name)
32+
@($BuildCommandInvocation.BoundParameters.GetEnumerator().ForEach{ "-{0} '{1}'" -f $_.Key, $_.Value })
33+
) -join ' ')"
3034
$BuildInfo = GetBuildInfo -BuildManifest $BuildManifest -BuildCommandInvocation $BuildCommandInvocation
3135

32-
# These errors are caused by trying to parse valid module manifests without compiling the module first
33-
$ErrorsWeIgnore = "^" + @(
34-
"Modules_InvalidRequiredModulesinModuleManifest"
35-
"Modules_InvalidRootModuleInModuleManifest"
36-
) -join "|^"
37-
3836
# Finally, add all the information in the module manifest to the return object
39-
$ModuleInfo = Get-Module (Get-Item $BuildInfo.ModuleManifest).FullName -ListAvailable -WarningAction SilentlyContinue -ErrorAction SilentlyContinue -ErrorVariable Problems
40-
41-
# If there are any problems that count, fail
42-
if ($Problems = $Problems.Where( {$_.FullyQualifiedErrorId -notmatch $ErrorsWeIgnore})) {
43-
foreach ($problem in $Problems) {
44-
Write-Error $problem
45-
}
46-
throw "Unresolvable problems in module manifest"
37+
if ($ModuleInfo = ImportModuleManifest $BuildInfo.SourcePath) {
38+
# Update the module manifest with our build configuration and output it
39+
Update-Object -InputObject $ModuleInfo -UpdateObject $BuildInfo
40+
} else {
41+
throw "Unresolvable problems in module manifest: '$($BuildInfo.SourcePath)'"
4742
}
48-
49-
# Update the ModuleManifest with our build configuration
50-
$ModuleInfo = Update-Object -InputObject $ModuleInfo -UpdateObject $BuildInfo
51-
$ModuleInfo = Update-Object -InputObject $ModuleInfo -UpdateObject @{ DefaultCommandPrefix = $ModuleInfo.Prefix; Prefix = "" }
52-
53-
$ModuleInfo
5443
}

Source/Private/MoveUsingStatements.ps1

+4-5
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,12 @@ function MoveUsingStatements {
3232

3333
# Avoid modifying the file if there's no Parsing Error caused by Using Statements or other errors
3434
if (!$ParseErrors.Where{$_.ErrorId -eq 'UsingMustBeAtStartOfScript'}) {
35-
Write-Debug "No Using Statement Error found."
35+
Write-Debug "No using statement errors found."
3636
return
3737
}
3838
# Avoid modifying the file if there's other parsing errors than Using Statements misplaced
3939
if ($ParseErrors.Where{$_.ErrorId -ne 'UsingMustBeAtStartOfScript'}) {
40-
Write-Warning "Parsing errors found. Skipping Moving Using statements."
40+
Write-Warning "Parsing errors found. Skipping moving using statements."
4141
return
4242
}
4343

@@ -71,9 +71,8 @@ function MoveUsingStatements {
7171
)
7272

7373
if ($ParseErrors) {
74-
Write-Warning "Oops, it seems that we introduced parsing error(s) while moving the Using Statements. Cancelling changes."
75-
}
76-
else {
74+
Write-Warning "We introduced parsing error(s) while attempting to move using statements. Cancelling changes."
75+
} else {
7776
$null = Set-Content -Value $ScriptText -Path $RootModule -Encoding $Encoding
7877
}
7978
}

Source/Private/ResolveBuildManifest.ps1

+2-4
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ function ResolveBuildManifest {
99
if ((Split-Path $SourcePath -Leaf) -eq 'build.psd1') {
1010
$BuildManifest = $SourcePath
1111
} elseif (Test-Path $SourcePath -PathType Leaf) {
12-
# When you pass the ModuleManifest as parameter, you must have the Build Manifest in the same folder
12+
# When you pass the SourcePath as parameter, you must have the Build Manifest in the same folder
1313
$BuildManifest = Join-Path (Split-Path -Parent $SourcePath) [Bb]uild.psd1
1414
} else {
1515
# It's a container, assume the Build Manifest is directly under
@@ -19,9 +19,7 @@ function ResolveBuildManifest {
1919
# Make sure we are resolving the absolute path to the manifest, and test it exists
2020
$ResolvedBuildManifest = (Resolve-Path $BuildManifest -ErrorAction SilentlyContinue).Path
2121

22-
if (-Not ($ResolvedBuildManifest)) {
23-
throw "Couldn't resolve the Build Manifest at $BuildManifest"
24-
} else {
22+
if ($ResolvedBuildManifest) {
2523
$ResolvedBuildManifest
2624
}
2725

Source/Private/SetModuleContent.ps1

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ function SetModuleContent {
3131
[string]$Encoding = $(if($IsCoreCLR) { "UTF8Bom" } else { "UTF8" })
3232
)
3333
begin {
34+
Write-Debug "SetModuleContent WorkingDirectory $WorkingDirectory"
3435
Push-Location $WorkingDirectory -StackName SetModuleContent
3536
$ContentStarted = $false # There has been no content yet
3637

Source/build.psd1

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Use this file to override the default parameter values used by the `Build-Module`
22
# command when building the module (see `Get-Help Build-Module -Full` for details).
33
@{
4-
Path = "ModuleBuilder.psd1"
4+
ModuleManifest = "ModuleBuilder.psd1"
55
# Subsequent relative paths are to the ModuleManifest
66
OutputDirectory = "../"
77
VersionedOutputDirectory = $true

0 commit comments

Comments
 (0)