|
| 1 | +# Platform Abstraction Layer — Architecture & Implementation Plan |
| 2 | + |
| 3 | +## 1. Problem Statement |
| 4 | + |
| 5 | +mcpp has ~45+ `#if defined(...)` blocks scattered across 10+ source files for platform-specific logic. This causes: |
| 6 | +- **stdin leakage**: subprocess calls on macOS don't close stdin → first-run hangs |
| 7 | +- **Code duplication**: `self_exe_path()` duplicated in `config.cppm` and `ninja_backend.cppm` |
| 8 | +- **Inconsistent abstractions**: `process.cppm` wraps some calls, but many files still use raw `std::system()`/`popen()` |
| 9 | +- **Maintenance burden**: adding platform support requires editing 10+ files |
| 10 | + |
| 11 | +## 2. Target Architecture |
| 12 | + |
| 13 | +``` |
| 14 | +src/platform/ |
| 15 | +├── platform.cppm # Unified facade (re-exports all platform capabilities) |
| 16 | +├── common.cppm # Cross-platform constants + compile-time detection |
| 17 | +├── process.cppm # Unified process execution (auto-closes stdin on POSIX) |
| 18 | +├── fs.cppm # Platform filesystem ops (exe path, file lock, which) |
| 19 | +├── env.cppm # Environment variable operations |
| 20 | +├── shell.cppm # Shell quoting + command building |
| 21 | +├── macos.cppm # macOS: xcrun, SDK discovery, Xcode CLT detection |
| 22 | +├── linux.cppm # Linux: /proc, LD_LIBRARY_PATH, patchelf |
| 23 | +└── windows.cppm # Windows: vswhere, MSVC, _putenv_s, registry |
| 24 | +``` |
| 25 | + |
| 26 | +## 3. Module Responsibilities |
| 27 | + |
| 28 | +### common.cppm — Compile-time constants & platform detection |
| 29 | +- Platform booleans: `is_windows`, `is_macos`, `is_linux` |
| 30 | +- Binary naming: `exe_suffix`, `static_lib_ext`, `shared_lib_ext`, `lib_prefix` |
| 31 | +- Platform name string |
| 32 | +- Null redirect string |
| 33 | + |
| 34 | +### process.cppm — Unified process execution (CORE FIX) |
| 35 | +- `capture()` — run command, capture stdout (auto `</dev/null` on POSIX) |
| 36 | +- `run_silent()` — run without caring about output |
| 37 | +- `run_streaming()` — stream stdout line-by-line via callback |
| 38 | +- All subprocess calls go through here → stdin leak impossible |
| 39 | + |
| 40 | +### fs.cppm — Platform filesystem operations |
| 41 | +- `self_exe_path()` — current executable path (Win: GetModuleFileName, macOS: _NSGetExecutablePath, Linux: /proc/self/exe) |
| 42 | +- `FileLock` — RAII exclusive file lock (Win: LockFileEx, POSIX: flock) |
| 43 | +- `which()` — find executable (Win: `where`, POSIX: `command -v`) |
| 44 | + |
| 45 | +### env.cppm — Environment variable operations |
| 46 | +- `set()` / `get()` — platform-aware env var manipulation |
| 47 | +- `build_env_prefix()` — construct env prefix for commands |
| 48 | + |
| 49 | +### shell.cppm — Shell quoting |
| 50 | +- `quote()` — platform-aware shell argument quoting |
| 51 | +- `silent_redirect` — full silent redirect string |
| 52 | + |
| 53 | +### macos.cppm — macOS-specific |
| 54 | +- `has_xcode_clt()` — check Xcode Command Line Tools |
| 55 | +- `sdk_path()` — xcrun SDK discovery |
| 56 | +- `runtime_lib_dirs()` — macOS library search paths |
| 57 | + |
| 58 | +### linux.cppm — Linux-specific |
| 59 | +- `build_ld_library_path()` — LD_LIBRARY_PATH construction |
| 60 | +- `runtime_lib_dirs()` — Linux library search paths |
| 61 | + |
| 62 | +### windows.cppm — Windows-specific |
| 63 | +- `find_visual_studio()` — VS discovery via vswhere/env/paths |
| 64 | +- `find_std_module_source()` — MSVC STL module source |
| 65 | +- `prepend_path()` — Windows PATH manipulation |
| 66 | + |
| 67 | +### platform.cppm — Unified facade |
| 68 | +- Re-exports all common modules |
| 69 | +- Conditionally exports platform-specific modules |
| 70 | + |
| 71 | +## 4. Migration Impact |
| 72 | + |
| 73 | +| Source File | Changes | Effort | |
| 74 | +|-------------|---------|--------| |
| 75 | +| `config.cppm` | Use `fs::self_exe_path()`, `fs::which()`, `process::run_silent()` | Medium | |
| 76 | +| `xlings.cppm` | Use `shell::quote()`, `process::run_streaming()`, `env::*` | Large | |
| 77 | +| `process.cppm` | DELETE — absorbed by `platform/process.cppm` + `platform/shell.cppm` | Delete | |
| 78 | +| `probe.cppm` | Use `fs::which()`, `macos::sdk_path()` | Medium | |
| 79 | +| `bmi_cache.cppm` | Use `fs::FileLock` | Small | |
| 80 | +| `ninja_backend.cppm` | Use `fs::self_exe_path()` | Small | |
| 81 | +| `flags.cppm` | Use `common` constants | Small | |
| 82 | +| `clang.cppm` | Use `windows::find_std_module_source()` | Small | |
| 83 | +| `msvc.cppm` | Discovery logic moves to `platform/windows.cppm` | Medium | |
| 84 | +| `cli.cppm` | Use `common::name` | Small | |
| 85 | + |
| 86 | +## 5. Implementation Phases |
| 87 | + |
| 88 | +### Phase 1: Skeleton (parallel-safe) |
| 89 | +Create directory + `common.cppm` (move from existing `platform.cppm`) |
| 90 | + |
| 91 | +### Phase 2: Core modules (parallelizable) |
| 92 | +- 2a: `process.cppm` — includes stdin fix |
| 93 | +- 2b: `fs.cppm` — self_exe_path + FileLock + which |
| 94 | +- 2c: `env.cppm` + `shell.cppm` |
| 95 | +- 2d: `macos.cppm` + `linux.cppm` + `windows.cppm` |
| 96 | +- 2e: `platform.cppm` facade |
| 97 | + |
| 98 | +### Phase 3: Consumer migration |
| 99 | +Migrate all files to use new platform modules, remove old `process.cppm` |
| 100 | + |
| 101 | +### Phase 4: Build config + CI verification |
| 102 | +Update `mcpp.toml`, verify on all platforms |
0 commit comments