Add foundation tracing and platform build wrapper
This commit is contained in:
@@ -54,7 +54,8 @@ add_custom_target(panopainter_modernization_status
|
||||
|
||||
add_library(pp_foundation STATIC
|
||||
src/foundation/binary_stream.cpp
|
||||
src/foundation/parse.cpp)
|
||||
src/foundation/parse.cpp
|
||||
src/foundation/trace.cpp)
|
||||
target_include_directories(pp_foundation
|
||||
PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src")
|
||||
|
||||
@@ -64,7 +64,7 @@ ctest --preset desktop-fast --build-config Debug
|
||||
powershell -ExecutionPolicy Bypass -File scripts\automation\test.ps1 -Preset desktop-fast -Configuration Debug
|
||||
powershell -ExecutionPolicy Bypass -File scripts\automation\build.ps1 -Preset windows-msvc-default -Configuration Debug -Target pano_cli
|
||||
cmake --preset android-arm64
|
||||
cmake --build --preset android-arm64 --target pp_foundation pano_cli pp_foundation_binary_stream_tests pp_foundation_parse_tests
|
||||
powershell -ExecutionPolicy Bypass -File scripts\automation\platform-build.ps1 -Presets android-arm64
|
||||
```
|
||||
|
||||
Known local toolchain state:
|
||||
@@ -73,7 +73,8 @@ Known local toolchain state:
|
||||
- Local Visual Studio generator selected by CMake: Visual Studio 17 2022
|
||||
- Android SDK: `C:\Users\omara\AppData\Local\Android\Sdk`
|
||||
- Android NDK: `C:\Users\omara\AppData\Local\Android\Sdk\ndk\29.0.14206865`
|
||||
- Android arm64 headless configure/build passes through root CMake.
|
||||
- Android arm64 headless configure/build passes through root CMake and the
|
||||
`platform-build` automation wrapper.
|
||||
- `vcpkg` is not on PATH yet; see DEBT-0007.
|
||||
|
||||
Known warnings after the current CMake app build:
|
||||
|
||||
@@ -28,7 +28,7 @@ agent or engineer to remove them without reconstructing context from chat.
|
||||
| DEBT-0006 | Open | Modernization | `pano_cli create-document` validates and emits JSON command contracts but does not yet invoke the legacy document/app model | The document model has not been extracted from `Canvas`/`App` yet | `pano_cli create-document --width 64 --height 32 --layers 2`; CTest `pano_cli_create_document_smoke` | Replace command contract implementation with real `pp_document` creation once Phase 4 extracts the document model |
|
||||
| DEBT-0007 | Open | Modernization | `vcpkg.json` exists but CMake is not yet using a validated vcpkg toolchain on this machine | `vcpkg` is not available on PATH and Visual Studio reports manifest mode is disabled | `cmake --preset windows-msvc-default` currently configures with vendored dependencies | Add validated vcpkg toolchain/preset integration for desktop, Android, and Apple triplets |
|
||||
| DEBT-0008 | Open | Modernization | `windows-msvc-default` preset is used for local validation because the VS 2026 generator is not installed here | The target VS 2026 preset must remain, but this machine configures with Visual Studio 17 2022 | `cmake --preset windows-msvc-default`; `ctest --preset desktop-fast --build-config Debug` | Validate `windows-vs2026-x64` on a machine with Visual Studio 2026 installed and make it the default Windows validation preset |
|
||||
| DEBT-0009 | Open | Modernization | Android root CMake validation currently builds headless targets only, not APK/package variants | Platform app entrypoints still live in legacy Gradle/CMake projects and need Phase 6 alignment | `cmake --preset android-arm64`; `cmake --build --preset android-arm64 --target pp_foundation pano_cli pp_foundation_binary_stream_tests pp_foundation_parse_tests` | Android standard, Quest, and Focus/Wave package targets consume shared component targets and have package smoke commands |
|
||||
| DEBT-0009 | Open | Modernization | Android root CMake validation currently builds headless targets only, not APK/package variants | Platform app entrypoints still live in legacy Gradle/CMake projects and need Phase 6 alignment | `powershell -ExecutionPolicy Bypass -File scripts\automation\platform-build.ps1 -Presets android-arm64` | Android standard, Quest, and Focus/Wave package targets consume shared component targets and have package smoke commands |
|
||||
|
||||
## Closed Debt
|
||||
|
||||
|
||||
@@ -242,9 +242,10 @@ Gate:
|
||||
Goal: make each component reachable by automated tools and future agents.
|
||||
|
||||
Status: in progress. `tests/` exists, `desktop-fast` runs headlessly, and
|
||||
PowerShell/bash wrappers exist for configure/build/test/analyze. `pano_cli`
|
||||
exists with a first JSON automation command for validating create-document
|
||||
inputs; full document/app integration is debt-tracked as DEBT-0006.
|
||||
PowerShell/bash wrappers exist for configure/build/test/analyze/platform-build.
|
||||
`pano_cli` exists with a first JSON automation command for validating
|
||||
create-document inputs; full document/app integration is debt-tracked as
|
||||
DEBT-0006.
|
||||
|
||||
Implementation tasks:
|
||||
|
||||
@@ -300,8 +301,9 @@ Goal: split libraries while keeping current app behavior.
|
||||
Status: started. `pp_foundation` exists with binary stream utilities and
|
||||
boundary/overread tests. It also owns strict decimal `uint32` parsing used by
|
||||
`pano_cli`, with rejection tests for empty, signed, mixed, and overflowing
|
||||
input. Continue extracting legacy-safe utilities before moving assets, paint,
|
||||
or document behavior.
|
||||
input. A deterministic `TraceRecorder` now records component/name/thread/frame
|
||||
and stroke timing spans with invalid-end tests. Continue extracting legacy-safe
|
||||
utilities before moving assets, paint, or document behavior.
|
||||
|
||||
Implementation tasks:
|
||||
|
||||
@@ -501,25 +503,26 @@ Last verified on 2026-05-31:
|
||||
|
||||
```powershell
|
||||
cmake --preset windows-msvc-default
|
||||
cmake --build --preset windows-msvc-default --config Debug --target pp_foundation_binary_stream_tests pp_foundation_parse_tests pano_cli PanoPainter
|
||||
cmake --build --preset windows-msvc-default --config Debug --target pp_foundation_binary_stream_tests pp_foundation_parse_tests pp_foundation_trace_tests pano_cli PanoPainter
|
||||
ctest --preset desktop-fast --build-config Debug
|
||||
powershell -ExecutionPolicy Bypass -File scripts\automation\test.ps1 -Preset desktop-fast -Configuration Debug
|
||||
powershell -ExecutionPolicy Bypass -File scripts\automation\build.ps1 -Preset windows-msvc-default -Configuration Debug -Target pano_cli
|
||||
cmake --preset android-arm64
|
||||
cmake --build --preset android-arm64 --target pp_foundation pano_cli pp_foundation_binary_stream_tests pp_foundation_parse_tests
|
||||
powershell -ExecutionPolicy Bypass -File scripts\automation\platform-build.ps1 -Presets android-arm64
|
||||
```
|
||||
|
||||
Results:
|
||||
|
||||
- `pp_foundation_binary_stream_tests` passed.
|
||||
- `pp_foundation_parse_tests` passed.
|
||||
- `pp_foundation_trace_tests` passed.
|
||||
- `pano_cli_create_document_smoke` passed.
|
||||
- `PanoPainter.exe` built through CMake at
|
||||
`out/build/windows-msvc-default/Debug/PanoPainter.exe`.
|
||||
- PowerShell build/test automation wrappers return JSON summaries and passed
|
||||
local smoke checks.
|
||||
- Android arm64 configured with NDK 29.0.14206865 and compiled headless
|
||||
foundation/tool/test targets.
|
||||
- Android arm64 configured with NDK 29.0.14206865 through the platform-build
|
||||
wrapper and compiled headless foundation/tool/test targets.
|
||||
- Known remaining warnings: legacy project/vendor diagnostics, Visual Studio
|
||||
vcpkg-manifest warning, `LNK4099` missing libyuv PDBs, and `LNK4098` runtime
|
||||
library conflict from retained vendor binaries.
|
||||
|
||||
52
scripts/automation/platform-build.ps1
Normal file
52
scripts/automation/platform-build.ps1
Normal file
@@ -0,0 +1,52 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string[]]$Presets = @("android-arm64"),
|
||||
[string[]]$Targets = @("pp_foundation", "pano_cli", "pp_foundation_binary_stream_tests", "pp_foundation_parse_tests", "pp_foundation_trace_tests")
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$started = Get-Date
|
||||
$results = @()
|
||||
$overallExitCode = 0
|
||||
|
||||
foreach ($preset in $Presets) {
|
||||
& cmake --preset $preset
|
||||
$configureExitCode = $LASTEXITCODE
|
||||
if ($configureExitCode -ne 0) {
|
||||
$overallExitCode = $configureExitCode
|
||||
$results += [ordered]@{
|
||||
preset = $preset
|
||||
stage = "configure"
|
||||
exitCode = $configureExitCode
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
$buildArgs = @("--build", "--preset", $preset)
|
||||
foreach ($target in $Targets) {
|
||||
$buildArgs += @("--target", $target)
|
||||
}
|
||||
|
||||
& cmake @buildArgs
|
||||
$buildExitCode = $LASTEXITCODE
|
||||
if ($buildExitCode -ne 0 -and $overallExitCode -eq 0) {
|
||||
$overallExitCode = $buildExitCode
|
||||
}
|
||||
|
||||
$results += [ordered]@{
|
||||
preset = $preset
|
||||
stage = "build"
|
||||
targets = $Targets
|
||||
exitCode = $buildExitCode
|
||||
}
|
||||
}
|
||||
|
||||
$elapsed = [int]((Get-Date) - $started).TotalMilliseconds
|
||||
[ordered]@{
|
||||
command = "platform-build"
|
||||
exitCode = $overallExitCode
|
||||
elapsedMs = $elapsed
|
||||
results = $results
|
||||
} | ConvertTo-Json -Compress -Depth 6
|
||||
|
||||
exit $overallExitCode
|
||||
29
scripts/automation/platform-build.sh
Normal file
29
scripts/automation/platform-build.sh
Normal file
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env sh
|
||||
set -u
|
||||
|
||||
preset="${1:-android-arm64}"
|
||||
shift || true
|
||||
targets="${*:-pp_foundation pano_cli pp_foundation_binary_stream_tests pp_foundation_parse_tests pp_foundation_trace_tests}"
|
||||
start="$(date +%s)"
|
||||
|
||||
cmake --preset "$preset"
|
||||
configure_exit="$?"
|
||||
if [ "$configure_exit" -ne 0 ]; then
|
||||
end="$(date +%s)"
|
||||
elapsed_ms="$(( (end - start) * 1000 ))"
|
||||
printf '{"command":"platform-build","preset":"%s","stage":"configure","exitCode":%s,"elapsedMs":%s}\n' "$preset" "$configure_exit" "$elapsed_ms"
|
||||
exit "$configure_exit"
|
||||
fi
|
||||
|
||||
build_args=""
|
||||
for target in $targets; do
|
||||
build_args="$build_args --target $target"
|
||||
done
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
cmake --build --preset "$preset" $build_args
|
||||
build_exit="$?"
|
||||
end="$(date +%s)"
|
||||
elapsed_ms="$(( (end - start) * 1000 ))"
|
||||
printf '{"command":"platform-build","preset":"%s","targets":"%s","exitCode":%s,"elapsedMs":%s}\n' "$preset" "$targets" "$build_exit" "$elapsed_ms"
|
||||
exit "$build_exit"
|
||||
98
src/foundation/trace.cpp
Normal file
98
src/foundation/trace.cpp
Normal file
@@ -0,0 +1,98 @@
|
||||
#include "foundation/trace.h"
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace pp::foundation {
|
||||
|
||||
Result<TraceSpanId> TraceRecorder::begin_span(TraceSpanDesc desc, std::uint64_t start_us)
|
||||
{
|
||||
if (desc.component.empty()) {
|
||||
return Result<TraceSpanId>::failure(
|
||||
Status::invalid_argument("trace component must not be empty"));
|
||||
}
|
||||
|
||||
if (desc.name.empty()) {
|
||||
return Result<TraceSpanId>::failure(
|
||||
Status::invalid_argument("trace span name must not be empty"));
|
||||
}
|
||||
|
||||
if (next_id_ == std::numeric_limits<TraceSpanId>::max()) {
|
||||
return Result<TraceSpanId>::failure(
|
||||
Status::out_of_range("trace span id space is exhausted"));
|
||||
}
|
||||
|
||||
const auto id = next_id_++;
|
||||
ActiveSpan span;
|
||||
span.id = id;
|
||||
span.component.assign(desc.component);
|
||||
span.name.assign(desc.name);
|
||||
span.desc = desc;
|
||||
span.desc.component = span.component;
|
||||
span.desc.name = span.name;
|
||||
span.start_us = start_us;
|
||||
span.active = true;
|
||||
active_spans_.push_back(span);
|
||||
|
||||
return Result<TraceSpanId>::success(id);
|
||||
}
|
||||
|
||||
Status TraceRecorder::end_span(TraceSpanId id, std::uint64_t end_us)
|
||||
{
|
||||
ActiveSpan* span = find_active_span(id);
|
||||
if (span == nullptr) {
|
||||
return Status::out_of_range("trace span id is not active");
|
||||
}
|
||||
|
||||
if (end_us < span->start_us) {
|
||||
return Status::invalid_argument("trace span cannot end before it starts");
|
||||
}
|
||||
|
||||
TraceEvent event;
|
||||
event.component = span->component;
|
||||
event.name = span->name;
|
||||
event.thread_id = span->desc.thread_id;
|
||||
event.frame_id = span->desc.frame_id;
|
||||
event.stroke_id = span->desc.stroke_id;
|
||||
event.start_us = span->start_us;
|
||||
event.duration_us = end_us - span->start_us;
|
||||
events_.push_back(event);
|
||||
|
||||
span->active = false;
|
||||
return Status::success();
|
||||
}
|
||||
|
||||
std::span<const TraceEvent> TraceRecorder::events() const noexcept
|
||||
{
|
||||
return events_;
|
||||
}
|
||||
|
||||
std::size_t TraceRecorder::active_span_count() const noexcept
|
||||
{
|
||||
std::size_t count = 0;
|
||||
for (const auto& span : active_spans_) {
|
||||
if (span.active) {
|
||||
++count;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
void TraceRecorder::clear() noexcept
|
||||
{
|
||||
active_spans_.clear();
|
||||
events_.clear();
|
||||
next_id_ = 1;
|
||||
}
|
||||
|
||||
TraceRecorder::ActiveSpan* TraceRecorder::find_active_span(TraceSpanId id) noexcept
|
||||
{
|
||||
for (auto& span : active_spans_) {
|
||||
if (span.active && span.id == id) {
|
||||
return &span;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
60
src/foundation/trace.h
Normal file
60
src/foundation/trace.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace pp::foundation {
|
||||
|
||||
using TraceSpanId = std::uint64_t;
|
||||
|
||||
struct TraceSpanDesc {
|
||||
std::string_view component;
|
||||
std::string_view name;
|
||||
std::uint64_t thread_id = 0;
|
||||
std::uint64_t frame_id = 0;
|
||||
std::uint64_t stroke_id = 0;
|
||||
};
|
||||
|
||||
struct TraceEvent {
|
||||
std::string component;
|
||||
std::string name;
|
||||
std::uint64_t thread_id = 0;
|
||||
std::uint64_t frame_id = 0;
|
||||
std::uint64_t stroke_id = 0;
|
||||
std::uint64_t start_us = 0;
|
||||
std::uint64_t duration_us = 0;
|
||||
};
|
||||
|
||||
class TraceRecorder {
|
||||
public:
|
||||
[[nodiscard]] Result<TraceSpanId> begin_span(TraceSpanDesc desc, std::uint64_t start_us);
|
||||
[[nodiscard]] Status end_span(TraceSpanId id, std::uint64_t end_us);
|
||||
|
||||
[[nodiscard]] std::span<const TraceEvent> events() const noexcept;
|
||||
[[nodiscard]] std::size_t active_span_count() const noexcept;
|
||||
|
||||
void clear() noexcept;
|
||||
|
||||
private:
|
||||
struct ActiveSpan {
|
||||
TraceSpanId id = 0;
|
||||
TraceSpanDesc desc;
|
||||
std::string component;
|
||||
std::string name;
|
||||
std::uint64_t start_us = 0;
|
||||
bool active = false;
|
||||
};
|
||||
|
||||
[[nodiscard]] ActiveSpan* find_active_span(TraceSpanId id) noexcept;
|
||||
|
||||
std::vector<ActiveSpan> active_spans_;
|
||||
std::vector<TraceEvent> events_;
|
||||
TraceSpanId next_id_ = 1;
|
||||
};
|
||||
|
||||
}
|
||||
@@ -26,6 +26,16 @@ add_test(NAME pp_foundation_parse_tests COMMAND pp_foundation_parse_tests)
|
||||
set_tests_properties(pp_foundation_parse_tests PROPERTIES
|
||||
LABELS "foundation;desktop-fast")
|
||||
|
||||
add_executable(pp_foundation_trace_tests
|
||||
foundation/trace_tests.cpp)
|
||||
target_link_libraries(pp_foundation_trace_tests PRIVATE
|
||||
pp_foundation
|
||||
pp_test_harness)
|
||||
|
||||
add_test(NAME pp_foundation_trace_tests COMMAND pp_foundation_trace_tests)
|
||||
set_tests_properties(pp_foundation_trace_tests PROPERTIES
|
||||
LABELS "foundation;desktop-fast")
|
||||
|
||||
if(TARGET pano_cli)
|
||||
add_test(NAME pano_cli_create_document_smoke
|
||||
COMMAND pano_cli create-document --width 64 --height 32 --layers 2)
|
||||
|
||||
109
tests/foundation/trace_tests.cpp
Normal file
109
tests/foundation/trace_tests.cpp
Normal file
@@ -0,0 +1,109 @@
|
||||
#include "foundation/trace.h"
|
||||
#include "test_harness.h"
|
||||
|
||||
using pp::foundation::StatusCode;
|
||||
using pp::foundation::TraceRecorder;
|
||||
using pp::foundation::TraceSpanDesc;
|
||||
|
||||
namespace {
|
||||
|
||||
void records_completed_spans_with_context(pp::tests::Harness& h)
|
||||
{
|
||||
TraceRecorder recorder;
|
||||
|
||||
const auto id = recorder.begin_span(
|
||||
TraceSpanDesc {
|
||||
.component = "paint",
|
||||
.name = "stroke_commit",
|
||||
.thread_id = 7,
|
||||
.frame_id = 11,
|
||||
.stroke_id = 13,
|
||||
},
|
||||
100);
|
||||
|
||||
PP_EXPECT(h, id.ok());
|
||||
PP_EXPECT(h, recorder.active_span_count() == 1U);
|
||||
PP_EXPECT(h, recorder.end_span(id.value(), 145).ok());
|
||||
PP_EXPECT(h, recorder.active_span_count() == 0U);
|
||||
PP_EXPECT(h, recorder.events().size() == 1U);
|
||||
|
||||
const auto& event = recorder.events()[0];
|
||||
PP_EXPECT(h, event.component == "paint");
|
||||
PP_EXPECT(h, event.name == "stroke_commit");
|
||||
PP_EXPECT(h, event.thread_id == 7U);
|
||||
PP_EXPECT(h, event.frame_id == 11U);
|
||||
PP_EXPECT(h, event.stroke_id == 13U);
|
||||
PP_EXPECT(h, event.start_us == 100U);
|
||||
PP_EXPECT(h, event.duration_us == 45U);
|
||||
}
|
||||
|
||||
void rejects_invalid_span_descriptions(pp::tests::Harness& h)
|
||||
{
|
||||
TraceRecorder recorder;
|
||||
|
||||
const auto no_component = recorder.begin_span(
|
||||
TraceSpanDesc { .component = "", .name = "load" },
|
||||
1);
|
||||
const auto no_name = recorder.begin_span(
|
||||
TraceSpanDesc { .component = "assets", .name = "" },
|
||||
1);
|
||||
|
||||
PP_EXPECT(h, !no_component.ok());
|
||||
PP_EXPECT(h, no_component.status().code == StatusCode::invalid_argument);
|
||||
PP_EXPECT(h, !no_name.ok());
|
||||
PP_EXPECT(h, no_name.status().code == StatusCode::invalid_argument);
|
||||
PP_EXPECT(h, recorder.events().empty());
|
||||
}
|
||||
|
||||
void rejects_bad_end_calls_without_recording_events(pp::tests::Harness& h)
|
||||
{
|
||||
TraceRecorder recorder;
|
||||
const auto id = recorder.begin_span(
|
||||
TraceSpanDesc { .component = "renderer", .name = "readback" },
|
||||
50);
|
||||
|
||||
PP_EXPECT(h, id.ok());
|
||||
|
||||
const auto backwards = recorder.end_span(id.value(), 49);
|
||||
PP_EXPECT(h, !backwards.ok());
|
||||
PP_EXPECT(h, backwards.code == StatusCode::invalid_argument);
|
||||
PP_EXPECT(h, recorder.active_span_count() == 1U);
|
||||
PP_EXPECT(h, recorder.events().empty());
|
||||
|
||||
PP_EXPECT(h, recorder.end_span(id.value(), 51).ok());
|
||||
const auto duplicate = recorder.end_span(id.value(), 52);
|
||||
PP_EXPECT(h, !duplicate.ok());
|
||||
PP_EXPECT(h, duplicate.code == StatusCode::out_of_range);
|
||||
PP_EXPECT(h, recorder.events().size() == 1U);
|
||||
}
|
||||
|
||||
void clear_resets_events_and_span_ids(pp::tests::Harness& h)
|
||||
{
|
||||
TraceRecorder recorder;
|
||||
const auto first = recorder.begin_span(
|
||||
TraceSpanDesc { .component = "ui", .name = "layout" },
|
||||
10);
|
||||
PP_EXPECT(h, first.ok());
|
||||
PP_EXPECT(h, recorder.end_span(first.value(), 20).ok());
|
||||
recorder.clear();
|
||||
|
||||
const auto second = recorder.begin_span(
|
||||
TraceSpanDesc { .component = "ui", .name = "layout" },
|
||||
30);
|
||||
PP_EXPECT(h, second.ok());
|
||||
PP_EXPECT(h, second.value() == first.value());
|
||||
PP_EXPECT(h, recorder.events().empty());
|
||||
PP_EXPECT(h, recorder.active_span_count() == 1U);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
pp::tests::Harness harness;
|
||||
harness.run("records_completed_spans_with_context", records_completed_spans_with_context);
|
||||
harness.run("rejects_invalid_span_descriptions", rejects_invalid_span_descriptions);
|
||||
harness.run("rejects_bad_end_calls_without_recording_events", rejects_bad_end_calls_without_recording_events);
|
||||
harness.run("clear_resets_events_and_span_ids", clear_resets_events_and_span_ids);
|
||||
return harness.finish();
|
||||
}
|
||||
Reference in New Issue
Block a user