Adding Runtime Features
How to add a new class or feature to sharp-runtime following project conventions.
Before You Start
- Check
plan.sqlite3(tabletask) to see if the class is tracked and what its status is — the porting workflow itself is documented inprompt.md - Check
NEXT.mdfor the current status summary and next unprocessed items - Confirm the class is part of
System.*— sharp-runtime only targets the .NET System namespace - Consult the .NET reference docs to understand the expected API surface
Step 1: Choose the Owning Module
Choose an existing physical module before creating files. The module owns the feature's public headers, implementation, tests, and dependency declaration. For example, a new fundamental System type normally belongs in modules/core/; an I/O type belongs in modules/io/.
If no existing module owns the API area, add a new module directory, register it once in cmake/SharpRuntimeModules.cmake, and declare its dependencies in its local CMakeLists.txt. Do not add new shared top-level include/, src/, or tests/ trees.
Step 2: Create the Header
For a core type, create modules/core/include/System/[Namespace]/ClassName.hpp:
// SPDX-License-Identifier: MIT
// Copyright (C) 2024 Robert Vokac
// Based on .NET Runtime (MIT License)
#pragma once
#include <System/Object.hpp>
#include <SharpRuntime/Prop.hpp>
namespace System {
// Or: namespace System::IO etc.
/**
* @brief One-line description.
* @status Partial
*/
class MyClass : public Object {
public:
MyClass();
DDATA(int, SomeProperty)
void SomeMethod(const std::string& arg);
GetTypeNameHPP();
};
} // namespace System
Step 3: Create the Implementation
Create modules/core/src/System/[Namespace]/MyClass.cpp in the same owning module:
// SPDX-License-Identifier: MIT
// Copyright (C) 2024 Robert Vokac
// Based on .NET Runtime (MIT License)
#include <System/MyClass.hpp>
namespace System {
GetTypeNameCPP(MyClass, "System.MyClass")
IDATA(MyClass, int, SomeProperty)
MyClass::MyClass() : someProperty_(0) {}
void MyClass::SomeMethod(const std::string& arg) {
// implementation
}
} // namespace System
sharp_runtime_register_module() discovers src/*.cpp and
tests/*.cpp only inside the owning module. You do not need to edit its
CMakeLists.txt for another file in that module, but you must declare dependencies
correctly and register a genuinely new module.
Step 4: Write Tests
Create modules/core/tests/System/MyClassTest.cpp (or the matching test path in the owning module):
// SPDX-License-Identifier: MIT
#include <gtest/gtest.h>
#include <System/MyClass.hpp>
using namespace System;
TEST(MyClassTest, ConstructorDefault) {
MyClass obj;
EXPECT_EQ(obj.getSomePropertyProperty(), 0);
}
TEST(MyClassTest, SomeMethod) {
MyClass obj;
obj.SomeMethod("test");
// assert expected state
}
TEST(MyClassTest, GetTypeName) {
MyClass obj;
EXPECT_EQ(obj.GetTypeName(), "System.MyClass");
}
All and use
scripts/run_component_tests.sh build.
Step 5: Platform Guards (if needed)
If your implementation uses platform-specific APIs:
- Keep platform-specific
#includes in.cppfiles only - Use
#ifdef _WIN32/#elif defined(__EMSCRIPTEN__)/#elseguards - If a feature is POSIX-only, document it in the header with a Doxygen note
- Provide stub implementations for unsupported platforms rather than compilation errors
Step 6: Update Status
Update the @status Doxygen comment in the header file as the implementation progresses:
Todo— planned, not startedStub— header exists, empty/minimal implementationPartial— key functionality works, some methods missingImplemented— all planned methods doneVerified— fully tested against .NET behavior
Checklist
- SPDX header on every file
#pragma oncein headerGetTypeNameHPP()in class bodyGetTypeNameCPP()in .cpp- All properties use
DDATA/DGETTERmacros - Files are under one owning
modules/<module>directory - Public, private, and test-only dependencies are declared with the correct visibility
- No POSIX
#includes in .hpp files - Tests written and passing
- Zero new warnings after build
@statuscomment reflects actual state