Build-PSBuildMarkdown unloads a module the caller had loaded
Chưa có ai nhận issue này.
Đánh giá
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức phù hợp với người mới
- 68/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Khá rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Công nghệ
- powershell
- Lĩnh vực
- build-system
Hướng nghiên cứu
Bắt đầu từ điểm vào Build-PSBuildMarkdown, đặc biệt là phần import ở dòng 54 và phần dọn dẹp ở dòng 152. Đọc tests/fixtures/FixtureHelpers.psm1 và sử dụng Invoke-PSBuildCommandInJob khi kiểm tra các ảnh hưởng đến session. Công việc được xem là hoàn tất khi tài liệu vẫn phản ánh việc refresh bắt buộc, trong khi các module của caller đã được tải trước đó vẫn có thể sử dụng, kể cả trên nhánh không có export.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
Build-PSBuildMarkdown ends with an unconditional Remove-Module $ModuleName in its finally. Remove-Module -Name removes every loaded module with that name, including a copy the caller loaded before the call and that this function never imported. The caller gets their session emptied out as a side effect of generating documentation.
What happens
$moduleInfo = Import-Module "$ModulePath/$ModuleName.psd1" -Global -Force -PassThru # line 54
try {
...
} catch {
Write-Error ($LocalizedData.FailedToGenerateMarkdownHelp -f $_)
} finally {
Remove-Module $ModuleName # line 152
}
The import is scoped by path; the removal is scoped by name. Nothing records what was loaded before the call, and nothing is put back.
Measured
A caller loads their own copy of a module, calls Build-PSBuildMarkdown against a different path with the same module name, and afterwards their copy is gone:
BEFORE instances=1
AFTER (different paths) instances=0
AFTER Get-Widget works? no
With the caller's copy and the documented copy at the same path, the effect is the same and the message is blunter:
BEFORE Get-Module PSBuildTestFixture -> 1 instance(s)
BEFORE Get-Widget works? yes
AFTER Get-Module PSBuildTestFixture -> 0 instance(s)
AFTER Get-Widget works? no: The term 'Get-Widget' is not recognized as a name of a cmdlet,
function, script file, or executable program.
Docs generated: 3 file(s)
The documentation is produced correctly. The session is left broken.
Three further facts that shape the fix, all measured:
Scoping the Remove-Module is not sufficient on its own. When the caller had loaded the same path, Import-Module -Global -Force at line 54 replaces their instance before the finally is ever reached:
same-path: instances=1; original instance object still present? False
So even a Remove-Module -ModuleInfo $moduleInfo would leave the caller with nothing, because the thing it removes is what used to be theirs. (When the paths differ, the two instances coexist — instances=2 — and only the name-based removal takes both.)
-Force is nevertheless load-bearing and must not simply be dropped. Without it, Import-Module on an already-loaded module is a no-op that returns the cached PSModuleInfo, reporting the previous build's exported commands:
v1 exports: Get-Alpha
re-import WITHOUT -Force reports: Get-Alpha
re-import WITH -Force reports: Get-Alpha, Get-Beta
$moduleInfo is passed straight to New-MarkdownCommandHelp -ModuleInfo, so without -Force the docs would silently document a stale command surface — a worse failure than this one, because it is invisible.
The zero-export early return unloads the caller's module too, despite generating nothing:
BEFORE: Get-Alpha -> alpha
AFTER : Get-Alpha -> gone
Markdown generated: 0 file(s)
That is the FunctionsToExport = @() path — the #201 shape — where the function warns NoCommandsExported and returns. return inside try still runs the finally.
Why it matters, and who it reaches
This is not an edge case reached by unusual configuration. It is on by default for essentially every consumer:
$PSBBuildDependency = @('StageFiles', 'BuildHelp')
$PSBBuildHelpDependency = @('GenerateMarkdown', 'GenerateMAML')
Build depends on BuildHelp depends on GenerateMarkdown, so any consumer running build.ps1 at all runs this, unless they override $PSBBuildDependency. A survey of 75 consumer psakefiles carried out during this investigation found 1 that does — the one quoted below. That figure has not been independently re-measured here; the default dependency chain above, and the PSDepend override, have been.
A real consumer has already hit it and worked around it. PowerShellOrg/PSDepend removed the documentation tasks from its build, with the reason in the file:
# Pre-set before -FromModule so PowerShellBuild 0.7.x's null-check doesn't override it.
# Skips BuildHelp (GenerateMarkdown) — doc generation is not needed in the test pipeline
# and Build-PSBuildMarkdown has a Remove-Module scope bug specific to PSDepend.
$PSBBuildDependency = @('StageFiles')
That is a consumer diagnosing this defect correctly, deciding it was not worth reporting, and paying for it by giving up generated documentation. It is not "specific to PSDepend" — it is specific to any build session that had the module loaded, which is the common case for an interactive ./build.ps1 in a module's own repository.
The symptom a consumer sees is a command that worked a moment ago no longer being recognised, in a session where nothing obviously removed it. The natural conclusions are "my build corrupted something" or "PowerShell is confused", not "the documentation task unloaded my module".
Options
- Record what was loaded and restore it. Capture
Get-Module -Name $ModuleNamebefore the import, remove by-ModuleInforather than by name in thefinally, and re-import anything that was previously loaded. This is the shape that actually works, because it addresses both halves — the over-broad removal and the-Forceeviction — and it keeps-Forceso the documented surface stays current. Cost: the restored module is a fresh import, not literally the caller's original instance, so a caller holding aPSModuleInforeference still sees it go stale. That is a much smaller problem than the command disappearing. - Leave the module loaded. Delete the
Remove-Moduleentirely and accept that generating documentation leaves the built module imported. Simplest possible change, and it makes the function's side effect additive rather than destructive. The cost is that a subsequent task in the same session runs against a module imported from the build output, which is usually what you want but is a real behaviour change and could mask a staleness problem elsewhere. - Do the whole thing in a separate process. The function's own test file already does this —
Invoke-PSBuildCommandInJobintests/fixtures/FixtureHelpers.psm1exists specifically because these commands modify the session they run in. Moving the isolation into the function rather than into its tests makes the session hygiene the function's own responsibility. Most robust, most expensive, and it changes the error and progress reporting a consumer sees. - Document it. Say in the README that
GenerateMarkdownunloads the named module. This is what the current situation amounts to, undocumented, and it is not a fix — but it is better than nothing if the change is judged too large for now.
(1) is the option I would take. It preserves every current behaviour a consumer relies on, including the -Force refresh, and it is contained entirely within this function.
A related, separable point: -Global on the import at line 54 is unnecessary. PlatyPS 1.0.3 resolves the module through the PSModuleInfo object passed as -ModuleInfo, not by name, so it does not need the module in the global session state. Measured — the generated markdown is byte-for-byte identical with -Global removed:
withGlobal: 3 file(s): Get-Widget.md, PSBuildTestFixture.md, Set-Widget.md
noGlobal : 3 file(s): Get-Widget.md, PSBuildTestFixture.md, Set-Widget.md
Get-Widget.md identical=True
PSBuildTestFixture.md identical=True
Set-Widget.md identical=True
Dropping -Global reduces the blast radius of the import, but it does not fix the eviction: the name-based Remove-Module in the finally still reaches the caller's copy regardless of which session state the function imported into. Worth doing, worth doing separately, and not a substitute for (1).
Related: #222, the same defect in a worse form in Test-PSBuildPester — same root cause, probably one change. #201 for the zero-export path referenced above.
- Ngôn ngữ chính
- PowerShell
- Star
- 145
- Fork
- 27
- Merge trung bình
- 10 giờ 16 phút
- Pull request đã merge (30 ngày)
- 34
Hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của psake/PowerShellBuild
-
Nothing tests the Build-PSBuildUpdatableHelp branch that deletes a consumer's output directory Đang mởbug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
psake/PowerShellBuild#218 · 1 bình luận ·
-
bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 74/100
psake/PowerShellBuild#211 · 1 bình luận ·
-
CI: Install the built module from a local repository to verify install-time dependency behaviour Đang mởenhancement github_actions
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 68/100
psake/PowerShellBuild#229 ·
-
bug
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 55/100
psake/PowerShellBuild#222 ·
-
bug
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 55/100
psake/PowerShellBuild#220 ·
Tất cả issue của psake/PowerShellBuild
Issue tương tự
-
kind/bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 88/100
kubernetes-sigs/prow#953 · 1 bình luận ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 88/100
zephyrproject-rtos/zephyr#119726 ·
-
comp/dashboard P3 type/bug
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 86/100
NousResearch/hermes-agent#117722 ·
-
Use zstd compression? Đang mởNeeds Design Priority: Wishlist
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 84/100
elementary/flatpak-platform#253 ·
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 78/100
objectionary/hone-maven-plugin#1060 ·