Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MrL8i59N6i3M5A48rJ3u4J
570 lines
21 KiB
Markdown
570 lines
21 KiB
Markdown
# xo-ppsink Carve-Out Implementation Plan
|
||
|
||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||
|
||
**Goal:** Extract the arena-free pretty-print front-end (`PpSink`/`FlatSink`/`Pretty`/`FunctionStyle`) from `xo-indentlog2` into a new low-level repo `xo-ppsink`, so subsystems below `xo-arena` (e.g. `xo-arena`, `xo-facet`) can pretty-print without depending on the arena-backed engine.
|
||
|
||
**Architecture:** `xo-ppsink` is a small shared library (namespace `xo::print`, include stem `xo/ppsink/`) depending only on `xo-cmake` (build tooling) + `xo-timeutil` (propagated, anticipating a later `xo::scope` migration). `xo-indentlog2` keeps the arena-backed engine (`PpState`, `PrettySink`, `PpToken*`, `PpConfig`, and the `Pretty` specializations) and gains a dependency on `xo-ppsink`. End-state level order: `xo-timeutil → xo-ppsink → … → xo-arena → xo-indentlog2`.
|
||
|
||
**Tech Stack:** C++20, CMake + xo-cmake macros, Nix (`pkgs/*.nix` + `xo.nix`/`ci.nix`/`shells.nix`), Catch2 for tests, git-subrepo for the umbrella.
|
||
|
||
## Global Constraints
|
||
|
||
- Namespace stays **`xo::print`** — do NOT rename to `xo::ppsink`.
|
||
- Cross-repo includes use angle brackets `<xo/…>`; same-repo includes use quotes `"…"`.
|
||
- Test `.cpp` files: **no anonymous namespaces** (unity-build friendly); give test-local types unique suffixed names (e.g. `Point_PpsinkTest`) to avoid ODR clashes.
|
||
- `xo-ppsink` sits below `xo-testutil`, so its utest uses **plain Catch2** (`#define CATCH_CONFIG_MAIN`), never `xo/testutil`.
|
||
- The carve set is exactly: headers `PpSink.hpp`, `FlatSink.hpp`, `Pretty.hpp`, `FunctionStyle.hpp`; sources `PpSink.cpp`, `FlatSink.cpp`. Everything else in `xo-indentlog2/.../print/` stays.
|
||
- After any change to `xo-indentlog2` inter-repo deps, the `find_dependency()` set in `xo-indentlog2/cmake/xo_indentlog2Config.cmake.in` and the nix inputs in `pkgs/xo-indentlog2.nix` must stay consistent with the `xo_dependency()` calls.
|
||
|
||
---
|
||
|
||
## File Structure
|
||
|
||
**Task 1 creates (xo-ppsink):**
|
||
- `xo-ppsink/CMakeLists.txt` — top-level shared-lib project (scaffolded)
|
||
- `xo-ppsink/cmake/xo_ppsinkConfig.cmake.in` — exported config (`find_dependency(xo_timeutil)`)
|
||
- `xo-ppsink/include/xo/ppsink/{PpSink,FlatSink,Pretty,FunctionStyle}.hpp` — copies of the carve set (adjusted includes)
|
||
- `xo-ppsink/src/ppsink/{PpSink,FlatSink}.cpp` + `src/ppsink/CMakeLists.txt` — the shared lib `xo_ppsink`
|
||
- `xo-ppsink/utest/{ppsink_utest_main,FlatSink.test,Pretty.test}.cpp` + `utest/CMakeLists.txt` — new arena-free tests
|
||
- `pkgs/xo-ppsink.nix` — nix derivation
|
||
- `xo.nix`, `shells.nix`, `ci.nix`, umbrella `CMakeLists.txt` — register xo-ppsink
|
||
|
||
**Task 2 modifies (xo-indentlog2 consumes xo-ppsink):**
|
||
- Delete `xo-indentlog2/include/xo/indentlog2/print/{PpSink,FlatSink,Pretty,FunctionStyle}.hpp`
|
||
- Delete `xo-indentlog2/src/indentlog2/{PpSink,FlatSink}.cpp`
|
||
- `xo-indentlog2/src/indentlog2/CMakeLists.txt` — drop the two sources, add `xo_dependency(xo_ppsink)`
|
||
- `xo-indentlog2/include/xo/indentlog2/print/{PrettySink,PrettyVector,PrettyFunctionStyle}.hpp` — rewrite moved-header includes to `<xo/ppsink/…>`
|
||
- `xo-indentlog2/utest/{FlatSink,Pretty,PrettyVector}.test.cpp` — rewrite moved-header includes to `<xo/ppsink/…>`
|
||
- `xo-indentlog2/cmake/xo_indentlog2Config.cmake.in` — add `find_dependency(xo_ppsink)`
|
||
- `pkgs/xo-indentlog2.nix` — add `xo-ppsink` to `propagatedBuildInputs`
|
||
|
||
**Task 3 finalizes:**
|
||
- Regenerate `xo-cmake/etc/xo/subsystem-edges` from the umbrella; verify consistency and full builds.
|
||
|
||
---
|
||
|
||
## Task 1: Create and wire xo-ppsink (standalone, green)
|
||
|
||
**Files:** as listed under "Task 1 creates" above.
|
||
|
||
**Interfaces:**
|
||
- Produces: shared lib target `xo_ppsink`; headers under `xo/ppsink/`; namespace `xo::print` with `PpSink`, `PpSinkInserter`, `FlatSink`, `Pretty<T>`, `has_pretty<T>`, `pp_write(PpSink&, const T&)`, `FunctionStyle`. Nix attr `xo.ppsink`.
|
||
- Consumes: nothing new (xo-timeutil already exists).
|
||
|
||
- [ ] **Step 1: Scaffold the shared-lib skeleton into the existing subrepo dir**
|
||
|
||
`xo-ppsink/` already exists (subrepo: `.gitrepo`, `README.md`, `cmake/xo-bootstrap-macros.cmake`). Run the scaffolder to add the shared-lib skeleton (`CMakeLists.txt`, `cmake/xo_ppsinkConfig.cmake.in`, `include/xo/ppsink/`, `src/ppsink/`, `utest/`).
|
||
|
||
Run: `xo-cmake/bin/scaffold-subdir --mode=shared ppsink` (from the umbrella root; `scaffold-subdir` is a source-tree tool, not installed to `$PREFIX/bin`)
|
||
Expected: creates `xo-ppsink/CMakeLists.txt`, `xo-ppsink/cmake/xo_ppsinkConfig.cmake.in`, `xo-ppsink/src/ppsink/CMakeLists.txt`, `xo-ppsink/utest/CMakeLists.txt`, and empty `include/xo/ppsink/` / `src/ppsink/` trees. Target name is `xo_ppsink`.
|
||
|
||
Inspect the generated `xo-ppsink/CMakeLists.txt` and confirm it follows the `xo-indentlog2` shape: `project(xo_ppsink VERSION 1.0)`, `include(cmake/xo-bootstrap-macros.cmake)`, `xo_cxx_toplevel_options3()`, `add_subdirectory(src/ppsink)`, `add_subdirectory(utest)`, and `xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets)`. If the scaffold omits any of these, add them to match `xo-indentlog2/CMakeLists.txt`.
|
||
|
||
- [ ] **Step 2: Copy the four carve headers into `include/xo/ppsink/`, adjusting includes**
|
||
|
||
Copy each header from `xo-indentlog2/include/xo/indentlog2/print/` to `xo-ppsink/include/xo/ppsink/`, then apply the include edits below.
|
||
|
||
```bash
|
||
cd ~/proj/xo-umbrella2
|
||
mkdir -p xo-ppsink/include/xo/ppsink
|
||
cp xo-indentlog2/include/xo/indentlog2/print/PpSink.hpp xo-ppsink/include/xo/ppsink/PpSink.hpp
|
||
cp xo-indentlog2/include/xo/indentlog2/print/FlatSink.hpp xo-ppsink/include/xo/ppsink/FlatSink.hpp
|
||
cp xo-indentlog2/include/xo/indentlog2/print/Pretty.hpp xo-ppsink/include/xo/ppsink/Pretty.hpp
|
||
cp xo-indentlog2/include/xo/indentlog2/print/FunctionStyle.hpp xo-ppsink/include/xo/ppsink/FunctionStyle.hpp
|
||
```
|
||
|
||
Edit `xo-ppsink/include/xo/ppsink/PpSink.hpp` — replace the include block
|
||
|
||
```cpp
|
||
#include <string_view>
|
||
#include <cstdint>
|
||
```
|
||
|
||
with (add `<iosfwd>` so `std::ostream` is only forward-declared; callers that stream pull in `<ostream>` themselves):
|
||
|
||
```cpp
|
||
#include <string_view>
|
||
#include <iosfwd>
|
||
#include <cstdint>
|
||
```
|
||
|
||
`FlatSink.hpp`, `Pretty.hpp`, `FunctionStyle.hpp` need no edits: their `#include "PpSink.hpp"` still resolves (same directory), and `FunctionStyle.hpp` has no includes.
|
||
|
||
- [ ] **Step 3: Copy the two carve sources into `src/ppsink/`, fixing the header path**
|
||
|
||
```bash
|
||
cd ~/proj/xo-umbrella2
|
||
mkdir -p xo-ppsink/src/ppsink
|
||
cp xo-indentlog2/src/indentlog2/PpSink.cpp xo-ppsink/src/ppsink/PpSink.cpp
|
||
cp xo-indentlog2/src/indentlog2/FlatSink.cpp xo-ppsink/src/ppsink/FlatSink.cpp
|
||
```
|
||
|
||
In `xo-ppsink/src/ppsink/PpSink.cpp` change `#include "print/PpSink.hpp"` → `#include <xo/ppsink/PpSink.hpp>`.
|
||
In `xo-ppsink/src/ppsink/FlatSink.cpp` change `#include "print/FlatSink.hpp"` → `#include <xo/ppsink/FlatSink.hpp>`.
|
||
|
||
- [ ] **Step 4: Write `src/ppsink/CMakeLists.txt`**
|
||
|
||
Replace the scaffolded `xo-ppsink/src/ppsink/CMakeLists.txt` with:
|
||
|
||
```cmake
|
||
# xo-ppsink/src/ppsink/CMakeLists.txt
|
||
|
||
set(SELF_LIB xo_ppsink)
|
||
set(SELF_SRCS
|
||
PpSink.cpp
|
||
FlatSink.cpp
|
||
)
|
||
|
||
xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS})
|
||
xo_install_include_tree3(include/xo/ppsink)
|
||
|
||
# ----------------------------------------------------------------
|
||
# dependencies
|
||
#
|
||
# NOTE: dependency set here must be kept consistent with
|
||
# xo-ppsink/cmake/xo_ppsinkConfig.cmake.in
|
||
|
||
xo_headeronly_dependency(${SELF_LIB} xo_timeutil)
|
||
|
||
# end CMakeLists.txt
|
||
```
|
||
|
||
- [ ] **Step 5: Write `cmake/xo_ppsinkConfig.cmake.in`**
|
||
|
||
Replace the scaffolded `xo-ppsink/cmake/xo_ppsinkConfig.cmake.in` with:
|
||
|
||
```cmake
|
||
@PACKAGE_INIT@
|
||
|
||
include(CMakeFindDependencyMacro)
|
||
|
||
# note: changes to find_dependency() calls here
|
||
# must coordinate with xo_(headeronly_)dependency() calls
|
||
# in xo-ppsink/src/ppsink/CMakeLists.txt
|
||
#
|
||
find_dependency(xo_timeutil)
|
||
|
||
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake")
|
||
include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Share.cmake")
|
||
check_required_components("@PROJECT_NAME@")
|
||
```
|
||
|
||
- [ ] **Step 6: Write the plain-catch2 utest main**
|
||
|
||
Create `xo-ppsink/utest/ppsink_utest_main.cpp`:
|
||
|
||
```cpp
|
||
/* @file ppsink_utest_main.cpp */
|
||
|
||
#define CATCH_CONFIG_MAIN
|
||
#include "catch2/catch.hpp"
|
||
|
||
/* end ppsink_utest_main.cpp */
|
||
```
|
||
|
||
- [ ] **Step 7: Write the FlatSink test (arena-free)**
|
||
|
||
Create `xo-ppsink/utest/FlatSink.test.cpp`:
|
||
|
||
```cpp
|
||
/* @file FlatSink.test.cpp */
|
||
|
||
#include <xo/ppsink/FlatSink.hpp>
|
||
#include <catch2/catch.hpp>
|
||
#include <sstream>
|
||
|
||
using xo::print::FlatSink;
|
||
using xo::print::PpSink;
|
||
|
||
TEST_CASE("flatsink-put-and-noop-structure", "[flatsink]") {
|
||
/* FlatSink writes put() text straight through; begin/split/end are no-ops */
|
||
std::stringstream ss;
|
||
FlatSink sink(ss);
|
||
|
||
sink.put("hello").begin().put("a").split().put("b").end().put("!");
|
||
|
||
REQUIRE(ss.str() == "helloab!");
|
||
}
|
||
|
||
TEST_CASE("flatsink-stream-open", "[flatsink]") {
|
||
/* stream_open returns an inserter that writes straight to the ostream */
|
||
std::stringstream ss;
|
||
FlatSink sink(ss);
|
||
{
|
||
auto ins = sink.stream_open(64);
|
||
ins << 42 << ' ' << "x";
|
||
ins.finish();
|
||
}
|
||
REQUIRE(ss.str() == "42 x");
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 8: Write the Pretty/pp_write test (arena-free)**
|
||
|
||
Create `xo-ppsink/utest/Pretty.test.cpp`. Note the unique `_PpsinkTest` type names and no anonymous namespace (unity-build friendly):
|
||
|
||
```cpp
|
||
/* @file Pretty.test.cpp */
|
||
|
||
#include <xo/ppsink/Pretty.hpp>
|
||
#include <xo/ppsink/FlatSink.hpp>
|
||
#include <catch2/catch.hpp>
|
||
#include <ostream>
|
||
#include <sstream>
|
||
|
||
using xo::print::FlatSink;
|
||
using xo::print::PpSink;
|
||
using xo::print::Pretty;
|
||
using xo::print::pp_write;
|
||
using xo::print::has_pretty;
|
||
|
||
/* a type that opts in to Pretty */
|
||
struct Point_PpsinkTest { int x; int y; };
|
||
|
||
/* a type that does NOT opt in -> pp_write falls back to operator<< */
|
||
struct Plain_PpsinkTest { int v; };
|
||
|
||
inline std::ostream &
|
||
operator<<(std::ostream & os, const Plain_PpsinkTest & p) {
|
||
return os << "Plain{" << p.v << "}";
|
||
}
|
||
|
||
namespace xo::print {
|
||
template <>
|
||
struct Pretty<Point_PpsinkTest> {
|
||
static void print(PpSink & sink, const Point_PpsinkTest & p) {
|
||
sink.begin().put("(");
|
||
pp_write(sink, p.x);
|
||
sink.put(",");
|
||
pp_write(sink, p.y);
|
||
sink.put(")").end();
|
||
}
|
||
};
|
||
}
|
||
|
||
TEST_CASE("pp_write-uses-specialization", "[pretty]") {
|
||
static_assert(has_pretty<Point_PpsinkTest>);
|
||
|
||
std::stringstream ss;
|
||
FlatSink sink(ss);
|
||
pp_write(sink, Point_PpsinkTest{1, 2}); /* begin/end are no-ops in FlatSink */
|
||
|
||
REQUIRE(ss.str() == "(1,2)");
|
||
}
|
||
|
||
TEST_CASE("pp_write-falls-back-to-operator<<", "[pretty]") {
|
||
static_assert(!has_pretty<Plain_PpsinkTest>);
|
||
|
||
std::stringstream ss;
|
||
FlatSink sink(ss);
|
||
pp_write(sink, Plain_PpsinkTest{7}); /* stream_open -> operator<< */
|
||
|
||
REQUIRE(ss.str() == "Plain{7}");
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 9: Write `utest/CMakeLists.txt`**
|
||
|
||
Replace the scaffolded `xo-ppsink/utest/CMakeLists.txt` with (mirrors `xo-timeutil/utest`, plain Catch2, no testutil):
|
||
|
||
```cmake
|
||
# xo-ppsink/utest/CMakeLists.txt
|
||
|
||
set(SELF_EXECUTABLE_NAME utest.ppsink)
|
||
set(SELF_SOURCE_FILES
|
||
ppsink_utest_main.cpp
|
||
FlatSink.test.cpp
|
||
Pretty.test.cpp)
|
||
|
||
if (ENABLE_TESTING)
|
||
xo_add_utest_executable(${SELF_EXECUTABLE_NAME} ${SELF_SOURCE_FILES})
|
||
xo_self_dependency(${SELF_EXECUTABLE_NAME} xo_ppsink)
|
||
xo_external_target_dependency(${SELF_EXECUTABLE_NAME} Catch2 Catch2::Catch2)
|
||
endif()
|
||
|
||
# end CMakeLists.txt
|
||
```
|
||
|
||
- [ ] **Step 10: Write `pkgs/xo-ppsink.nix`**
|
||
|
||
Create `pkgs/xo-ppsink.nix`:
|
||
|
||
```nix
|
||
{
|
||
# dependencies
|
||
lib, stdenv, cmake, catch2,
|
||
|
||
xo-cmake, xo-timeutil,
|
||
|
||
doCheck ? true,
|
||
} :
|
||
|
||
stdenv.mkDerivation (finalattrs:
|
||
{
|
||
name = "xo-ppsink";
|
||
version = "1.0";
|
||
|
||
src = ../xo-ppsink;
|
||
|
||
cmakeFlags = ["-DCMAKE_MODULE_PATH=${xo-cmake}/share/cmake"];
|
||
|
||
inherit doCheck;
|
||
|
||
# xo-ppsink's exported config + public headers pull in xo-timeutil,
|
||
# so consumers must receive it transitively.
|
||
propagatedBuildInputs = [ xo-timeutil ];
|
||
|
||
nativeBuildInputs = [ cmake
|
||
catch2
|
||
xo-cmake ];
|
||
})
|
||
```
|
||
|
||
- [ ] **Step 11: Register xo-ppsink in nix + umbrella**
|
||
|
||
In `xo.nix`, add after the `xo-timeutil` line (line ~29):
|
||
|
||
```nix
|
||
xo-ppsink = callPackage pkgs/xo-ppsink.nix { stdenv = jitStdenv; };
|
||
```
|
||
|
||
In `shells.nix`, add after the `timeutil` line (line ~325):
|
||
|
||
```nix
|
||
ppsink = pkgs.xo-ppsink;
|
||
```
|
||
|
||
In `ci.nix`, add `xo-ppsink` to the `inherit (xoPkgs)` list, immediately after `xo-timeutil`:
|
||
|
||
```nix
|
||
xo-timeutil
|
||
xo-ppsink
|
||
xo-subsys
|
||
```
|
||
|
||
In the umbrella `CMakeLists.txt`, add after `add_subdirectory(xo-timeutil …)` (so ppsink precedes indentlog2/arena):
|
||
|
||
```cmake
|
||
add_subdirectory(xo-ppsink) # arena-free pretty-print front-end (PpSink/FlatSink/Pretty)
|
||
```
|
||
|
||
`xo-ppsink` is already present in `xo-cmake/etc/xo/subsystem-list` (line 3) — no edit needed there.
|
||
|
||
- [ ] **Step 12: Build + test xo-ppsink via nix**
|
||
|
||
Run: `nix-build -A xo.ppsink --no-out-link`
|
||
Expected: builds and `doCheck` runs `utest.ppsink` — `All tests passed` (2 test files, 4 cases). Final output is a `/nix/store/…-xo-ppsink` path.
|
||
|
||
- [ ] **Step 13: Verify the umbrella still configures (ppsink added, indentlog2 untouched)**
|
||
|
||
Run: `cmake -S . -B .build >/tmp/ppsink-reconf.log 2>&1; echo exit=$?`
|
||
Expected: `exit=0`. (indentlog2 still has its own copies and does not yet depend on ppsink, so nothing is broken; ppsink is now an added subdir.)
|
||
|
||
- [ ] **Step 14: Commit**
|
||
|
||
```bash
|
||
cd ~/proj/xo-umbrella2
|
||
git add xo-ppsink pkgs/xo-ppsink.nix xo.nix shells.nix ci.nix CMakeLists.txt
|
||
git commit -m "xo-ppsink: new arena-free pretty-print front-end (PpSink/FlatSink/Pretty) [carve-out]"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 2: Migrate xo-indentlog2 onto xo-ppsink
|
||
|
||
**Files:** as listed under "Task 2 modifies" above.
|
||
|
||
**Interfaces:**
|
||
- Consumes: `xo_ppsink` target + `<xo/ppsink/{PpSink,FlatSink,Pretty,FunctionStyle}.hpp>` (namespace `xo::print`, unchanged) from Task 1.
|
||
- Produces: `xo-indentlog2` with no duplicate copies of the carve set; depends on `xo_ppsink`.
|
||
|
||
- [ ] **Step 1: Delete the carve set from xo-indentlog2**
|
||
|
||
```bash
|
||
cd ~/proj/xo-umbrella2
|
||
git rm xo-indentlog2/include/xo/indentlog2/print/PpSink.hpp \
|
||
xo-indentlog2/include/xo/indentlog2/print/FlatSink.hpp \
|
||
xo-indentlog2/include/xo/indentlog2/print/Pretty.hpp \
|
||
xo-indentlog2/include/xo/indentlog2/print/FunctionStyle.hpp \
|
||
xo-indentlog2/src/indentlog2/PpSink.cpp \
|
||
xo-indentlog2/src/indentlog2/FlatSink.cpp
|
||
```
|
||
|
||
- [ ] **Step 2: Rewrite includes in the staying headers**
|
||
|
||
In `xo-indentlog2/include/xo/indentlog2/print/PrettySink.hpp`: change `#include "PpSink.hpp"` → `#include <xo/ppsink/PpSink.hpp>`.
|
||
|
||
In `xo-indentlog2/include/xo/indentlog2/print/Pretty` — the primary `Pretty.hpp` was moved; the staying `PrettyVector.hpp` includes it: change `#include "Pretty.hpp"` → `#include <xo/ppsink/Pretty.hpp>` in `xo-indentlog2/include/xo/indentlog2/print/PrettyVector.hpp`.
|
||
|
||
In `xo-indentlog2/include/xo/indentlog2/print/PrettyFunctionStyle.hpp`: change
|
||
```cpp
|
||
#include "Pretty.hpp"
|
||
#include "FunctionStyle.hpp"
|
||
```
|
||
to
|
||
```cpp
|
||
#include <xo/ppsink/Pretty.hpp>
|
||
#include <xo/ppsink/FunctionStyle.hpp>
|
||
```
|
||
|
||
- [ ] **Step 3: Rewrite includes in the staying tests**
|
||
|
||
In `xo-indentlog2/utest/FlatSink.test.cpp`: change `#include "print/FlatSink.hpp"` → `#include <xo/ppsink/FlatSink.hpp>`.
|
||
In `xo-indentlog2/utest/Pretty.test.cpp`: change `#include "print/Pretty.hpp"` → `#include <xo/ppsink/Pretty.hpp>`, and `#include "print/FlatSink.hpp"` → `#include <xo/ppsink/FlatSink.hpp>`.
|
||
In `xo-indentlog2/utest/PrettyVector.test.cpp`: change `#include "print/FlatSink.hpp"` → `#include <xo/ppsink/FlatSink.hpp>`. (Its `#include "print/PrettyFunctionStyle.hpp"` stays — that header remains in indentlog2.)
|
||
|
||
- [ ] **Step 4: Drop the carve sources from the indentlog2 library**
|
||
|
||
In `xo-indentlog2/src/indentlog2/CMakeLists.txt`, remove `PpSink.cpp` and `FlatSink.cpp` from `SELF_SRCS`, and add the ppsink dependency. The `SELF_SRCS` block becomes:
|
||
|
||
```cmake
|
||
set(SELF_SRCS
|
||
LogStreambuf.cpp
|
||
LogConfig.cpp
|
||
LogBuffer.cpp
|
||
PrettySink.cpp
|
||
PpState.cpp
|
||
LineState.cpp
|
||
PpTokenStreambuf.cpp
|
||
PpToken.cpp
|
||
PpConfig.cpp
|
||
)
|
||
```
|
||
|
||
and the dependencies section becomes:
|
||
|
||
```cmake
|
||
xo_dependency(${SELF_LIB} xo_arena)
|
||
xo_dependency(${SELF_LIB} xo_ppsink)
|
||
```
|
||
|
||
- [ ] **Step 5: Add the ppsink find_dependency to the exported config**
|
||
|
||
In `xo-indentlog2/cmake/xo_indentlog2Config.cmake.in`, add below `find_dependency(xo_arena)`:
|
||
|
||
```cmake
|
||
find_dependency(xo_arena)
|
||
find_dependency(xo_ppsink)
|
||
```
|
||
|
||
- [ ] **Step 6: Add xo-ppsink to the indentlog2 nix inputs**
|
||
|
||
In `pkgs/xo-indentlog2.nix`, add `xo-ppsink` to the function args and to `propagatedBuildInputs`.
|
||
|
||
Change the xo-deps arg line
|
||
```nix
|
||
# xo dependencies
|
||
xo-arena,
|
||
xo-cmake,
|
||
```
|
||
to
|
||
```nix
|
||
# xo dependencies
|
||
xo-arena,
|
||
xo-ppsink,
|
||
xo-cmake,
|
||
```
|
||
|
||
Change
|
||
```nix
|
||
propagatedBuildInputs = [ xo-arena ];
|
||
```
|
||
to
|
||
```nix
|
||
propagatedBuildInputs = [ xo-arena xo-ppsink ];
|
||
```
|
||
|
||
- [ ] **Step 7: Build + test xo-indentlog2 via nix**
|
||
|
||
Run: `nix-build -A xo.indentlog2 --no-out-link`
|
||
Expected: builds; `doCheck` runs `utest.indentlog2` (LineState, LogBuffer, PpState, PpTokenStreambuf, PrettySink, FlatSink, Pretty, PrettyVector) — all pass. Confirms indentlog2 now resolves the carve set from `xo-ppsink` with no duplicate definitions.
|
||
|
||
- [ ] **Step 8: Verify the umbrella links indentlog2 against the ppsink sibling**
|
||
|
||
(Does not use `subsystem-edges` — that regen is Task 3. This checks the in-umbrella sibling wiring, since `xo-ppsink` was `add_subdirectory`'d in Task 1.)
|
||
|
||
Run:
|
||
```bash
|
||
cd ~/proj/xo-umbrella2
|
||
cmake -S . -B .build >/dev/null 2>&1 && \
|
||
cmake --build .build --target utest.indentlog2 -j && \
|
||
ctest --test-dir .build -R '^utest.indentlog2$' --output-on-failure
|
||
```
|
||
Expected: `xo_indentlog2` links `xo_ppsink`, `utest.indentlog2` builds and passes.
|
||
|
||
- [ ] **Step 9: Commit**
|
||
|
||
```bash
|
||
cd ~/proj/xo-umbrella2
|
||
git add xo-indentlog2 pkgs/xo-indentlog2.nix
|
||
git commit -m "xo-indentlog2: depend on xo-ppsink for PpSink/FlatSink/Pretty [levelize]"
|
||
```
|
||
|
||
---
|
||
|
||
## Task 3: Regenerate the dependency graph and final verification
|
||
|
||
**Files:** `xo-cmake/etc/xo/subsystem-edges` (regenerated).
|
||
|
||
**Interfaces:**
|
||
- Consumes: the finished xo-ppsink/xo-indentlog2 dependency wiring from Tasks 1–2.
|
||
- Produces: `subsystem-edges` reflecting the new `xo-ppsink` edges, consistent with `subsystem-list`.
|
||
|
||
- [ ] **Step 1: Reconfigure the umbrella to emit fresh edges**
|
||
|
||
Run: `cmake -S . -B .build >/tmp/edges.log 2>&1; echo exit=$?; grep xo_emit_dependency_edges /tmp/edges.log`
|
||
Expected: `exit=0`; the emit message prints `.build/xo-subsystem-edges.txt` was written.
|
||
|
||
- [ ] **Step 2: Confirm xo-ppsink edges appear**
|
||
|
||
Run: `grep -E 'ppsink' .build/xo-subsystem-edges.txt`
|
||
Expected: at least `xo-timeutil xo-ppsink` (ppsink depends on timeutil) and `xo-ppsink xo-indentlog2` (indentlog2 depends on ppsink).
|
||
|
||
- [ ] **Step 3: Update the checked-in edges file**
|
||
|
||
Run:
|
||
```bash
|
||
cd ~/proj/xo-umbrella2
|
||
cp .build/xo-subsystem-edges.txt xo-cmake/etc/xo/subsystem-edges
|
||
git diff --stat xo-cmake/etc/xo/subsystem-edges
|
||
```
|
||
Expected: the file gains the `ppsink` edges.
|
||
|
||
- [ ] **Step 4: Verify subsystem-list is still a valid topological order for the new edges**
|
||
|
||
Run: `tsort xo-cmake/etc/xo/subsystem-edges >/dev/null; echo "tsort exit=$?"`
|
||
Expected: `tsort exit=0` (no cycle). `xo-ppsink` already sits early in `subsystem-list` (after `xo-timeutil`, before `xo-indentlog2`), so no reorder is required. If `tsort` reports a loop, stop and investigate — a cycle means an accidental back-edge was introduced.
|
||
|
||
- [ ] **Step 5: Final whole-graph sanity via nix**
|
||
|
||
Run: `nix-build -A xo.ppsink -A xo.indentlog2 --no-out-link`
|
||
Expected: both build green.
|
||
|
||
- [ ] **Step 6: Verify the docker closure now resolves ppsink**
|
||
|
||
(Now that `subsystem-edges` carries `xo-ppsink xo-indentlog2`, `--with-deps` pulls ppsink into indentlog2's closure. Reinstall the tooling first so the container snapshot has the new edges.)
|
||
|
||
Run:
|
||
```bash
|
||
cd ~/proj/xo-umbrella2
|
||
cmake --install .build/xo-cmake 2>/dev/null || cmake -S xo-cmake -B .build/xo-cmake -DCMAKE_INSTALL_PREFIX=$HOME/local >/dev/null && cmake --install .build/xo-cmake >/dev/null
|
||
xo-docker-build --xo-root-dir xo-indentlog2 2>&1 | grep -E '::group::xo-ppsink|::group::xo-indentlog2|tests passed|Error' | tail
|
||
```
|
||
Expected: closure includes `xo-ppsink` before `xo-indentlog2`; both build; `utest.indentlog2` passes.
|
||
|
||
- [ ] **Step 7: Commit**
|
||
|
||
```bash
|
||
cd ~/proj/xo-umbrella2
|
||
git add xo-cmake/etc/xo/subsystem-edges
|
||
git commit -m "xo-cmake: regenerate subsystem-edges after xo-ppsink carve-out [BUILD]"
|
||
```
|
||
|
||
---
|
||
|
||
## Notes for the executor
|
||
|
||
- The carve headers are **copied** in Task 1 and the originals **deleted** in Task 2, deliberately: it keeps each task at a green, independently-reviewable state (Task 1 leaves indentlog2 self-contained; no translation unit ever includes both copies, so there is no ODR clash in the intermediate state).
|
||
- `PpSink.cpp` implements `PpSinkInserter` (ctor/dtor/`finish`); `FlatSink.cpp` implements the flat `PpSink`. Both are out-of-line, which is why xo-ppsink is a **shared** library rather than header-only.
|
||
- If `nix-build` complains it cannot find `-lxo_timeutil` / `xo_timeutilConfig.cmake` while building a consumer, the propagation is wrong — ensure `xo-timeutil` is in `propagatedBuildInputs` of `pkgs/xo-ppsink.nix` (not `nativeBuildInputs`), mirroring the ratio→reflectutil fix.
|