Back to Skiasharp

Add API Skill

.agents/skills/add-api/SKILL.md

3.119.46.4 KB
Original Source

Add API Skill

⚠️ Branch Protection (COMPLIANCE REQUIRED)

πŸ›‘ NEVER commit directly to protected branches. This is a policy violation.

RepositoryProtected BranchesRequired Action
SkiaSharp (parent)mainCreate feature branch first
externals/skia (submodule)main, skiasharpCreate feature branch first

BEFORE making any changes, create feature branches in BOTH repos:

bash
# Step 1: Create branch in SkiaSharp repo
git checkout -b dev/issue-NNNN-description

# Step 2: Create branch in submodule (REQUIRED for C API changes)
cd externals/skia
git checkout -b dev/issue-NNNN-description
cd ../..

❌ NEVER Do These

ShortcutConsequence
Commit directly to main or skiasharp branchesPolicy violation β€” blocks PR, requires revert
Edit *.generated.cs manuallyOverwrites on next generation; binding mismatch
Skip generator after C API changeC# bindings won't match C API
Skip native build after C API changeEntryPointNotFoundException at runtime β€” tests fail
Skip testsCan't verify functionality works
Skip tests because they failUnacceptable β€” fix the issue, don't skip
Commit C API without staging submoduleParent repo won't use your changes
Use externals-download after modifying C APIDownloaded natives don't have your new functions

Workflow Overview

1. Analyze C++ API  β†’  Find in Skia headers, identify pointer type
2. Add C API        β†’  Header + impl in externals/skia/
3. Commit Submodule β†’  Commit IN submodule, then stage in parent
4. Generate         β†’  Run pwsh ./utils/generate.ps1 (MANDATORY)
5. Add C# Wrapper   β†’  Validate params, call P/Invoke
6. Build Native     β†’  dotnet cake --target=externals-{platform} (MANDATORY)
7. Test             β†’  Run tests β€” must PASS (MANDATORY)

πŸ‘‰ Detailed checklists: references/checklists.md πŸ‘‰ Troubleshooting: references/troubleshooting.md


Phase 1: Analyze C++ API

Find the C++ API in Skia headers and identify:

  1. Pointer type β€” determines C# wrapper pattern:

    • Raw (T*) β†’ owns: false
    • Owned β†’ DisposeNative() deletes
    • Ref-counted (sk_sp<T>) β†’ ISKReferenceCounted or ISKNonVirtualReferenceCounted
  2. Error handling β€” returns null/bool? throws?

  3. Parameters β€” primitives, references, pointers, const-ness

πŸ‘‰ See ../../../documentation/dev/memory-management.md#pointer-type-decision-tree


Phase 2: Add C API

Location: externals/skia/include/c/ (header) and externals/skia/src/c/ (impl)

cpp
// Header (sk_canvas.h)
SK_C_API void sk_canvas_draw_circle(sk_canvas_t* canvas, float cx, float cy, 
                                     float radius, const sk_paint_t* paint);

// Implementation (sk_canvas.cpp)
void sk_canvas_draw_circle(sk_canvas_t* canvas, float cx, float cy,
                           float radius, const sk_paint_t* paint) {
    AsCanvas(canvas)->drawCircle(cx, cy, radius, *AsPaint(paint));
}

Conversion macros: AsType() unwraps, ToType() wraps. Use sk_ref_sp() for ref-counted params, .release() for ref-counted returns.


Phase 3: Commit Submodule

πŸ›‘ CRITICAL β€” This is where changes get lost

bash
# Step 1: Commit IN the submodule
cd externals/skia
git config user.email "[email protected]"  # first time only
git config user.name "Your Name"             # first time only
git add include/c/sk_*.h src/c/sk_*.cpp
git commit -m "Add sk_foo_bar to C API"

# Step 2: Stage submodule in parent
cd ../..  # Back to repo root
git add externals/skia

# Step 3: Verify
git status
# Must show: modified: externals/skia (new commits)

Phase 4: Generate Bindings

πŸ›‘ MANDATORY β€” Never skip, never edit generated files manually

bash
pwsh ./utils/generate.ps1
git diff binding/SkiaSharp/SkiaApi.generated.cs  # verify new function appears

Phase 5: Add C# Wrapper

Location: binding/SkiaSharp/SK*.cs

csharp
// Factory method β€” returns null on failure
public static SKImage FromPixels(SKImageInfo info, SKData data, int rowBytes)
{
    if (data == null)
        throw new ArgumentNullException(nameof(data));
    var cinfo = SKImageInfoNative.FromManaged(ref info);
    return GetObject(SkiaApi.sk_image_new_raster_data(&cinfo, data.Handle, (IntPtr)rowBytes));
}

// Instance method
public void DrawCircle(float cx, float cy, float radius, SKPaint paint)
{
    if (paint == null)
        throw new ArgumentNullException(nameof(paint));
    SkiaApi.sk_canvas_draw_circle(Handle, cx, cy, radius, paint.Handle);
}

Rules: Validate nulls, factory→null on fail, constructor→throw on fail, use overloads not defaults (ABI stability).


Phase 6: Build & Test

πŸ›‘ MANDATORY β€” You modified the C API, so you MUST build the native library

Step 1: Build Native Library (REQUIRED)

Since you added/modified C API functions in Phase 2, you MUST rebuild the native library before testing. The pre-built natives from externals-download do not contain your new functions.

bash
# macOS (Apple Silicon)
dotnet cake --target=externals-macos --arch=arm64

# macOS (Intel)
dotnet cake --target=externals-macos --arch=x64

# Windows (x64)
dotnet cake --target=externals-windows --arch=x64

# Linux (requires Docker)
dotnet cake --target=externals-linux --arch=x64

⚠️ Native builds take 10-30 minutes. Only build for platforms you can test on.

Step 2: Write Tests

csharp
[SkippableFact]
public void DrawCircleWorks()
{
    using var surface = SKSurface.Create(new SKImageInfo(100, 100));
    using var canvas = surface.Canvas;
    using var paint = new SKPaint { Color = SKColors.Red };
    
    canvas.DrawCircle(50, 50, 25, paint);
    
    using var image = surface.Snapshot();
    Assert.NotNull(image);
}

Step 3: Run Tests

bash
dotnet build binding/SkiaSharp/SkiaSharp.csproj
dotnet test tests/SkiaSharp.Tests.Console.sln

πŸ›‘ Tests MUST PASS. Do not skip tests. Do not claim completion if tests fail.

Skipping tests is only acceptable for hardware limitations (no GPU, no screen attached). EntryPointNotFoundException means you forgot to build the native library β€” go back to Step 1.

Tests must pass before claiming completion.