Skip to content

Commit 4cc2f40

Browse files
committed
feat(P4): guard mcpp pack on Windows with clear error + add design doc
Add a #if _WIN32 early-return at the top of mcpp::pack::run() that exits with a descriptive error message rather than failing silently on the subsequent ldd/patchelf/tar calls that are unavailable on Windows. Error message directs users to ci-windows.yml for CI-level packaging and notes that Windows PE packaging (DLL collection + zip) is planned. Add .agents/docs/2026-05-19-pack-windows-design.md documenting the full Windows pack design: DLL discovery strategy (dumpbin/PE-header walk), zip archive creation (PowerShell / libzip), staging layout, skip-list, and an implementation checklist for the future PR.
1 parent a6190dc commit 4cc2f40

2 files changed

Lines changed: 120 additions & 0 deletions

File tree

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# Windows Pack Design
2+
3+
**Date:** 2026-05-19
4+
**Status:** Planned (stub guard in place, implementation not yet started)
5+
6+
## Current state
7+
8+
`mcpp pack` is fully functional on Linux and macOS. On Windows it exits early
9+
with a clear error message directing users to the CI workflow:
10+
11+
```
12+
error: `mcpp pack` is not yet supported on Windows.
13+
Use the CI workflow (ci-windows.yml) to produce Windows zip packages.
14+
Windows PE packaging (DLL collection + zip) is planned.
15+
```
16+
17+
The guard lives at the top of `mcpp::pack::run()` in `src/pack/pack.cppm`.
18+
19+
## Why the current implementation cannot run on Windows
20+
21+
The POSIX implementation relies on three Linux/macOS-only mechanisms:
22+
23+
| Mechanism | POSIX usage | Windows equivalent |
24+
|---|---|---|
25+
| `LD_TRACE_LOADED_OBJECTS=1` | Tells the ELF dynamic linker to print deps without executing `main()` | No direct equivalent. Would need `dumpbin /dependents` (MSVC) or `ldd` emulation via `LoadLibraryEx` |
26+
| `patchelf` | Rewrites `RUNPATH` / `PT_INTERP` ELF headers in-place | Not applicable to PE/COFF. DLL search order is controlled by the OS loader and manifest, not embedded paths |
27+
| `tar -czf` | GNU tar — not universally present on Windows before Win11 22H2 | `Compress-Archive` (PowerShell), `7z`, or Win32 `CreateFile`/`MiniZip` |
28+
29+
## Planned Windows pack implementation
30+
31+
### Goal
32+
33+
Produce a self-contained `.zip` archive (not `.tar.gz`) that users can
34+
extract and run with no additional setup:
35+
36+
```
37+
<name>-<version>-x86_64-pc-windows-msvc.zip
38+
└── <name>-<version>-x86_64-pc-windows-msvc/
39+
├── <name>.exe
40+
├── *.dll (bundled DLLs, if any)
41+
└── README.md / LICENSE (if present)
42+
```
43+
44+
### DLL discovery
45+
46+
Replace `ldd_parse()` with a Win32 equivalent:
47+
48+
1. **Primary: `dumpbin /dependents <binary>`** — available when MSVC tools are
49+
on `PATH`. Produces a list of DLL names; resolve each against `PATH` /
50+
`%SystemRoot%\System32` / side-by-side assemblies.
51+
52+
2. **Fallback: `PE header walk`** — open the PE file, walk the Import Directory,
53+
extract DLL names. Can be implemented with `<windows.h>` + `ImageNtHeader`.
54+
55+
3. **Skip-list**: mirror the manylinux skip-list concept for Windows:
56+
`kernel32.dll`, `user32.dll`, `ntdll.dll`, `vcruntime*.dll` (Redist),
57+
`api-ms-win-*.dll` (API sets), `ucrtbase.dll`.
58+
59+
### Archive creation
60+
61+
Use `std::filesystem` to copy files into a staging directory, then produce
62+
the zip with one of:
63+
64+
- **PowerShell** `Compress-Archive` — available on all modern Windows.
65+
Invoke via `run_capture("powershell -Command \"Compress-Archive ..."`)`.
66+
Slow for large trees; fine for typical release packages.
67+
- **libzip / minizip** — statically linkable; avoid the PowerShell dependency.
68+
Preferred long-term.
69+
70+
### Format
71+
72+
- Output file: `.zip` (not `.tar.gz`) on Windows.
73+
- `pack::Format` enum needs a new `Zip` variant (or auto-select by platform).
74+
- `make_plan()` should derive the output extension from the target platform.
75+
76+
### Entry point
77+
78+
No shell wrapper needed on Windows — users double-click `<name>.exe` or run
79+
it from `cmd.exe` / PowerShell directly. If DLLs are bundled, they should be
80+
placed in the **same directory** as the executable (the Win32 loader checks
81+
`%EXE_DIR%` first, before `%PATH%`).
82+
83+
### Implementation checklist (for the future PR)
84+
85+
- [ ] Add `Format::Zip` (or `Format::ZipAuto`) to `pack::Format`
86+
- [ ] Implement `dumpbin_parse()` (or PE header walk fallback) in `pack.cppm`
87+
under `#if defined(_WIN32)`
88+
- [ ] Implement `make_zip()` (PowerShell or libzip) in `pack.cppm`
89+
- [ ] Remove the `#if defined(_WIN32)` early-return guard from `pack::run()`
90+
once the above are ready
91+
- [ ] Add a Windows-specific integration test to `ci-windows.yml`
92+
93+
### CI workflow (current workaround)
94+
95+
Until this is implemented, `ci-windows.yml` zips the raw build output with
96+
PowerShell `Compress-Archive`. This is good enough for CI artifacts but does
97+
not collect/bundle DLLs or apply the staging-directory layout.

src/pack/pack.cppm

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -536,6 +536,28 @@ make_tarball(const std::filesystem::path& stagingRoot,
536536
std::expected<void, Error>
537537
run(const Plan& plan, const mcpp::config::GlobalConfig& cfg)
538538
{
539+
#if defined(_WIN32)
540+
// `mcpp pack` is not yet supported on Windows.
541+
//
542+
// The current implementation relies on POSIX-only tools:
543+
// - LD_TRACE_LOADED_OBJECTS=1 (ELF dynamic linker trick; no equivalent
544+
// on Windows PE/COFF)
545+
// - ldd / patchelf (Linux ELF tools; not available on Windows)
546+
// - tar -czf (GNU tar; not universally present on Windows)
547+
//
548+
// For CI-produced Windows zip packages, use the ci-windows.yml workflow
549+
// which zips the MSVC/Clang build output directly.
550+
//
551+
// Windows PE packaging (DLL collection + zip) is planned.
552+
// See .agents/docs/2026-05-19-pack-windows-design.md for the design.
553+
(void)plan;
554+
(void)cfg;
555+
return std::unexpected(Error{
556+
"error: `mcpp pack` is not yet supported on Windows.\n"
557+
" Use the CI workflow (ci-windows.yml) to produce Windows zip packages.\n"
558+
" Windows PE packaging (DLL collection + zip) is planned."
559+
});
560+
#else
539561
using namespace detail;
540562
std::error_code ec;
541563

@@ -649,6 +671,7 @@ run(const Plan& plan, const mcpp::config::GlobalConfig& cfg)
649671
return r;
650672
}
651673
return {};
674+
#endif // !_WIN32
652675
}
653676

654677
} // namespace mcpp::pack

0 commit comments

Comments
 (0)