diff --git a/.github/skills/validate-samples/SKILL.md b/.github/skills/validate-samples/SKILL.md new file mode 100644 index 00000000000..f8f95d4501b --- /dev/null +++ b/.github/skills/validate-samples/SKILL.md @@ -0,0 +1,219 @@ +--- +name: validate-samples +description: >- + Build and validate SkiaSharp sample projects using CI-produced NuGet packages. + Downloads the latest CI artifacts, detects the preview version, and runs the + samples cake target to verify all samples compile correctly. + Triggers: "validate samples", "build samples", "test samples", "check samples build", + "run samples", "do the samples build", "samples CI", "verify sample builds". + Also use when asked to check if samples work after a code change, or when + investigating sample build failures. Use this skill proactively whenever the + user mentions building, testing, or validating any SkiaSharp sample project. +--- + +# Validate Samples + +Automates the workflow for building SkiaSharp samples against CI-produced NuGet packages. +The samples use package references (not project references) when built through the cake +target, so they need downloadable NuGet packages. + +## Quick Reference + +```bash +# Full workflow — clear cache, download, detect version, build all samples +dotnet cake --target=validate-samples-full + +# Or step by step: +rm -rf externals/package_cache/skiasharp* externals/package_cache/harfbuzzsharp* +dotnet cake --target=docs-download-output +# Detect version from downloaded nupkgs, then: +dotnet cake --target=samples --previewLabel=preview.0 --buildNumber=76 +``` + +## When to Use + +- After making changes to samples and wanting to verify they build +- When CI reports sample build failures and you need to reproduce locally +- When validating that a new SkiaSharp release doesn't break samples +- After merging changes that affect sample project files or dependencies + +## Step-by-Step Workflow + +### Step 1: Clear Cached Packages + +Old cached packages cause stale version resolution. Always clear before validating: + +```bash +rm -rf externals/package_cache/skiasharp* +rm -rf externals/package_cache/harfbuzzsharp* +``` + +If you suspect deeper caching issues, also clear the global NuGet cache: + +```bash +dotnet nuget locals all --clear +``` + +### Step 2: Download CI Packages + +Download the latest stable and preview NuGet packages from the CI feed: + +```bash +dotnet cake --target=docs-download-output +``` + +This downloads from the **SkiaSharp-CI** Azure DevOps feed: +`https://pkgs.dev.azure.com/xamarin/public/_packaging/SkiaSharp-CI/nuget/v3/index.json` + +Two package sets are fetched: +- `_nugets` — stable packages (e.g. `SkiaSharp.3.119.4.nupkg`) +- `_nugetspreview` — preview packages (e.g. `SkiaSharp.3.119.4-preview.0.76.nupkg`) + +Output goes to `output/nugets/`. + +> **Note:** This target clears `./output/` first. If you also need native binaries +> in `output/native/`, run `externals-download` first, then `docs-download-output`. + +### Step 3: Detect the Preview Version + +The preview label and build number must be passed to the samples target. Extract them +from the downloaded nupkg filenames: + +```bash +# Find a preview SkiaSharp package +PREVIEW_PKG=$(ls output/nugets/SkiaSharp.[0-9]*-*.nupkg 2>/dev/null | grep -v NativeAssets | head -1) +echo "Preview package: $PREVIEW_PKG" + +# Extract the suffix (everything after base version, before .nupkg) +# Example: SkiaSharp.3.119.4-preview.0.76.nupkg → preview.0.76 +SUFFIX=$(basename "$PREVIEW_PKG" | sed 's/^SkiaSharp\.[0-9]*\.[0-9]*\.[0-9]*-//' | sed 's/\.nupkg$//') +echo "Full suffix: $SUFFIX" + +# Split: preview label = everything before last dot, build number = last component +PREVIEW_LABEL=$(echo "$SUFFIX" | sed 's/\.[0-9]*$//') +BUILD_NUMBER=$(echo "$SUFFIX" | grep -o '[0-9]*$') +echo "Preview label: $PREVIEW_LABEL" +echo "Build number: $BUILD_NUMBER" +``` + +### Step 4: Build Samples + +Run the full samples pipeline with the detected version: + +```bash +dotnet cake --target=samples --previewLabel=$PREVIEW_LABEL --buildNumber=$BUILD_NUMBER +``` + +This runs three sub-targets in sequence: +1. **samples-generate** — Copies `samples/` to `output/samples/` and `output/samples-preview/`, + converting `` to `` using versions from `scripts/VERSIONS.txt` +2. **samples-prepare** — Clears cached SkiaSharp/HarfBuzz packages from `externals/package_cache`, + copies nupkgs next to Dockerfiles for Docker sample builds +3. **samples-run** — Builds every sample solution in the output directory + +#### Building a Single Sample + +Use the `--sample` filter to build just one: + +```bash +dotnet cake --target=samples --previewLabel=$PREVIEW_LABEL --buildNumber=$BUILD_NUMBER --sample=tvOS +dotnet cake --target=samples --previewLabel=$PREVIEW_LABEL --buildNumber=$BUILD_NUMBER --sample=Blazor +dotnet cake --target=samples --previewLabel=$PREVIEW_LABEL --buildNumber=$BUILD_NUMBER --sample=Android +``` + +## Version Construction Deep Dive + +### Two-step version system + +The CI feed and the NuGet packages use **different versioning**: + +1. **CI feed wrapper version** (e.g. `0.0.0-branch.main.2580`) — identifies the source build +2. **Real NuGet version** (e.g. `3.119.4-preview.0.76`) — the user-facing package version inside + +When downloading, cake uses `--gitBranch`/`--gitSha`/`--previewLabel` to resolve the CI wrapper version. +After extraction, the real nupkg versions are in `output/nugets/`. + +### How preview versions are built + +The cake build constructs the NuGet preview suffix from two arguments: + +```csharp +// build.cake:57-72 +var PREVIEW_LABEL = Argument("previewLabel", EnvironmentVariable("PREVIEW_LABEL") ?? "preview"); +var BUILD_NUMBER = Argument("buildNumber", EnvironmentVariable("BUILD_NUMBER") ?? "0"); +PREVIEW_NUGET_SUFFIX = $"{PREVIEW_LABEL}.{BUILD_NUMBER}"; +// Result: "preview.0.76" when PREVIEW_LABEL="preview.0" and BUILD_NUMBER="76" +``` + +The final NuGet version: `{base_version}-{PREVIEW_NUGET_SUFFIX}` +- Base version comes from `scripts/VERSIONS.txt` (e.g. `3.119.4`) +- The `.0` in `preview.0.76` is the **preview number** (first preview = 0, second = 1, etc.) + +### How download resolution works + +The `DownloadPackageAsync` function resolves the CI artifact version (checked in priority order): + +```csharp +// scripts/cake/UtilsManaged.cake:165-174 +if (PREVIEW_LABEL.StartsWith("pr.")) → "0.0.0-pr.{number}.*" +else if (!string.IsNullOrEmpty(GIT_SHA)) → "0.0.0-commit.{sha}.*" +else if (!string.IsNullOrEmpty(GIT_BRANCH_NAME)) → "0.0.0-branch.{name}.*" +else → "0.0.0-branch.main.*" +``` + +The `CreateSamplesDirectory()` function in `scripts/cake/samples.cake`: + +1. **``** → converted to `` using the project's + `` as the package ID and version from `VERSIONS.txt` +2. **Existing ``** → version updated from `VERSIONS.txt` +3. For SkiaSharp/HarfBuzzSharp packages, the preview suffix is appended + +Two output trees are created: +- `output/samples/` — stable versions +- `output/samples-preview/` — preview versions (with suffix) + +## Cake Arguments Reference + +| Argument | Env var | Default | Purpose | +|----------|---------|---------|---------| +| `--previewLabel` | `PREVIEW_LABEL` | `preview` | Preview suffix label | +| `--buildNumber` | `BUILD_NUMBER` | `0` | Build number for suffix | +| `--buildCounter` | `BUILD_COUNTER` | Same as buildNumber | Build counter for CI version | +| `--artifactsFeed` | — | SkiaSharp-CI URL | Override the NuGet feed | +| `--gitBranch` | `GIT_BRANCH_NAME` | `""` | Branch name for CI version | +| `--sample` | — | `""` | Filter to specific sample | + +## Troubleshooting + +### "The local source 'packages' doesn't exist" (Docker samples) +Docker samples (`Docker/Console`, `Docker/WebApi`) have their own `nuget.config` with a +local `packages` folder. This is by design — they're built via `run.ps1` inside Docker, +not `dotnet build`. The `samples-prepare` target copies nupkgs there automatically. + +### Platform-specific samples not building +Some platforms are disabled by default for local builds: +```bash +# These MSBuild properties enable optional platforms +-p:IsNetTVOSSupported=true +-p:IsNetTizenSupported=true +-p:IsNetMacOSSupported=true +``` + +### WinUI XAML compiler crash on .NET 10 +The WinUI sample may need a newer `Microsoft.WindowsAppSDK` version. This is a known +compatibility issue between older WinUI SDK versions and .NET 10. + +### Stale packages after repeated runs +If versions don't update between runs: +```bash +rm -rf externals/package_cache/skiasharp* externals/package_cache/harfbuzzsharp* +dotnet nuget locals all --clear +``` + +## Further Reading + +- [Building Samples](../../documentation/dev/building-samples.md) — Full developer documentation +- `build.cake` lines 57-83 — Version construction +- `build.cake` lines 473-651 — Sample targets +- `scripts/cake/samples.cake` — ProjectRef→PackageRef conversion logic +- `scripts/VERSIONS.txt` — Package version source of truth diff --git a/.github/skills/validate-samples/scripts/detect-preview-version.sh b/.github/skills/validate-samples/scripts/detect-preview-version.sh new file mode 100755 index 00000000000..e6a00513849 --- /dev/null +++ b/.github/skills/validate-samples/scripts/detect-preview-version.sh @@ -0,0 +1,38 @@ +#!/bin/bash +# detect-preview-version.sh +# Detects the preview label and build number from downloaded nupkg files in output/nugets/ +# Usage: source detect-preview-version.sh +# Sets: PREVIEW_LABEL, BUILD_NUMBER, PREVIEW_SUFFIX + +set -euo pipefail + +NUGETS_DIR="${1:-output/nugets}" + +# Find a preview SkiaSharp package (not NativeAssets, not HarfBuzz) +PREVIEW_PKG=$(ls "$NUGETS_DIR"/SkiaSharp.[0-9]*-*.nupkg 2>/dev/null | grep -v NativeAssets | head -1) + +if [ -z "$PREVIEW_PKG" ]; then + echo "ERROR: No preview SkiaSharp package found in $NUGETS_DIR" >&2 + echo "Run 'dotnet cake --target=docs-download-output' first." >&2 + exit 1 +fi + +# Extract filename +FILENAME=$(basename "$PREVIEW_PKG") +echo "Found: $FILENAME" + +# Extract suffix: SkiaSharp.3.119.4-preview.0.76.nupkg → preview.0.76 +SUFFIX=$(echo "$FILENAME" | sed 's/^SkiaSharp\.[0-9]*\.[0-9]*\.[0-9]*-//' | sed 's/\.nupkg$//') + +# Split on last dot: preview.0 + 76 +PREVIEW_LABEL=$(echo "$SUFFIX" | sed 's/\.[0-9]*$//') +BUILD_NUMBER=$(echo "$SUFFIX" | grep -o '[0-9]*$') + +echo "Preview label: $PREVIEW_LABEL" +echo "Build number: $BUILD_NUMBER" +echo "Full suffix: $SUFFIX" + +# Export for use by caller +export PREVIEW_LABEL +export BUILD_NUMBER +export PREVIEW_SUFFIX="$SUFFIX" diff --git a/documentation/dev/building-samples.md b/documentation/dev/building-samples.md new file mode 100644 index 00000000000..40fbe68efa3 --- /dev/null +++ b/documentation/dev/building-samples.md @@ -0,0 +1,201 @@ +# Building and Validating Samples + +This guide explains how to build SkiaSharp samples using CI-produced NuGet packages. The samples use **package references** (not project references) when built through the `samples` cake target, so they need downloadable NuGet packages to compile. + +## CI Artifacts Feed + +All CI builds publish wrapper packages to the **SkiaSharp-CI** Azure DevOps feed: + +``` +https://pkgs.dev.azure.com/xamarin/public/_packaging/SkiaSharp-CI/nuget/v3/index.json +``` + +These wrapper packages bundle the real NuGet packages inside their `tools/` directory: + +| Wrapper package | Contains | +|-----------------|----------| +| `_nativeassets` | Native binaries (per-platform frameworks/dylibs) | +| `_nugets` | Stable NuGet packages (e.g. `SkiaSharp.3.119.4.nupkg`) | +| `_nugetspreview` | Preview NuGet packages (e.g. `SkiaSharp.3.119.4-preview.0.76.nupkg`) | + +The wrapper packages use `0.0.0-{source}.{build}` versioning to identify their CI source. The actual NuGet packages inside have their real, user-facing version numbers. + +## Two-Step Process + +Building samples requires two separate sets of arguments because the CI feed version and the NuGet package version are different things: + +### Step 1: Download — select which CI build to fetch + +The `docs-download-output` target resolves the CI wrapper package version using these args (checked in priority order): + +| Argument | Resolves to | Use case | +|----------|------------|----------| +| `--previewLabel=pr.3553` | `0.0.0-pr.3553.*` | PR build | +| `--gitSha=abc123` | `0.0.0-commit.abc123.*` | Specific commit | +| `--gitBranch=release/3.119.4` | `0.0.0-branch.release.3.119.4.*` | Release branch | +| `--gitBranch=main` | `0.0.0-branch.main.*` | Main branch (nightly) | +| *(no args)* | `0.0.0-branch.main.*` | Default: latest from main | + +The `.*` wildcard selects the **latest** matching build from the feed. + +### Step 2: Build samples — use the real NuGet version + +After downloading, the extracted nupkgs in `output/nugets/` have real version numbers. The `samples` target needs `--previewLabel` and `--buildNumber` matching these real versions: + +```bash +# Detect from downloaded packages +ls output/nugets/SkiaSharp.3*-*.nupkg +# → SkiaSharp.3.119.4-preview.0.76.nupkg +# So: --previewLabel=preview.0 --buildNumber=76 +``` + +## NuGet Package Version Construction + +Preview package versions follow this pattern: + +``` +{base_version}-{PREVIEW_LABEL}.{BUILD_NUMBER} +``` + +- **base_version**: From `scripts/VERSIONS.txt` (e.g. `3.119.4`) +- **PREVIEW_LABEL**: The preview label (e.g. `preview.0` — first preview, `preview.1` — second, etc.) +- **BUILD_NUMBER**: The CI build counter + +**Example:** `3.119.4-preview.0.76` → `previewLabel=preview.0`, `buildNumber=76` + +## Cake Arguments + +### For downloading (`docs-download-output`, `externals-download`) + +These arguments control **which CI build** to fetch from the feed: + +| Argument | Environment variable | Default | Purpose | +|----------|---------------------|---------|---------| +| `--previewLabel` | `PREVIEW_LABEL` | `preview` | When starts with `pr.`, fetches PR build | +| `--gitSha` | `GIT_SHA` | `""` | Fetch by commit SHA | +| `--gitBranch` | `GIT_BRANCH_NAME` | `""` | Fetch by branch name | +| `--artifactsFeed` | — | SkiaSharp-CI URL | Override the NuGet feed | + +### For building samples (`samples`) + +These arguments control the **NuGet version suffix** used when rewriting package references: + +| Argument | Environment variable | Default | Purpose | +|----------|---------------------|---------|---------| +| `--previewLabel` | `PREVIEW_LABEL` | `preview` | Preview suffix label | +| `--buildNumber` | `BUILD_NUMBER` | `0` | Build number for suffix | +| `--sample` | — | `""` | Filter to build a specific sample | + +> **Note:** `--previewLabel` serves double duty: it selects the CI artifact during download AND forms the NuGet suffix during sample generation. For nightly builds from main, you typically run download with default args, then set `--previewLabel` and `--buildNumber` to match the extracted packages. + +## Cake Targets + +| Target | What it does | Output directory | +|--------|-------------|-----------------| +| `externals-download` | Downloads native binaries from CI feed | `output/native/{platform}/` | +| `docs-download-output` | Downloads stable + preview NuGet packages from CI feed | `output/nugets/` | +| `samples-generate` | Copies samples to `output/`, converts ProjectRef → PackageRef | `output/samples/`, `output/samples-preview/` | +| `samples-prepare` | Clears cached SkiaSharp/HarfBuzz packages, copies nupkgs for Docker | — | +| `samples-run` | Builds all generated samples from `output/` | — | +| `samples` | Runs generate → prepare → run in sequence | — | + +## Step-by-Step: Building Samples Locally + +### 1. Clear cached packages + +```bash +rm -rf externals/package_cache/skiasharp* +rm -rf externals/package_cache/harfbuzzsharp* +``` + +### 2. Download CI packages + +```bash +# Latest from main (default) +dotnet cake --target=docs-download-output + +# From a specific branch +dotnet cake --target=docs-download-output --gitBranch=release/3.119.4 + +# From a PR +dotnet cake --target=docs-download-output --previewLabel=pr.3553 + +# From a specific commit +dotnet cake --target=docs-download-output --gitSha=abc123def456 +``` + +This populates `output/nugets/` with the real `.nupkg` files extracted from the CI wrapper. + +### 3. Detect the preview version + +```bash +# Use the helper script +source .github/skills/validate-samples/scripts/detect-preview-version.sh +# Sets: PREVIEW_LABEL, BUILD_NUMBER + +# Or manually: +PREVIEW_PKG=$(ls output/nugets/SkiaSharp.[0-9]*-*.nupkg 2>/dev/null | grep -v NativeAssets | head -1) +SUFFIX=$(basename "$PREVIEW_PKG" | sed 's/^SkiaSharp\.[0-9]*\.[0-9]*\.[0-9]*-//' | sed 's/\.nupkg$//') +PREVIEW_LABEL=$(echo "$SUFFIX" | sed 's/\.[0-9]*$//') +BUILD_NUMBER=$(echo "$SUFFIX" | grep -o '[0-9]*$') +``` + +### 4. Build samples + +```bash +# Build all samples +dotnet cake --target=samples --previewLabel=$PREVIEW_LABEL --buildNumber=$BUILD_NUMBER + +# Build a single sample +dotnet cake --target=samples --previewLabel=$PREVIEW_LABEL --buildNumber=$BUILD_NUMBER --sample=tvOS +``` + +### Complete one-liner + +```bash +rm -rf externals/package_cache/skiasharp* externals/package_cache/harfbuzzsharp* && \ +dotnet cake --target=docs-download-output && \ +source .github/skills/validate-samples/scripts/detect-preview-version.sh && \ +dotnet cake --target=samples --previewLabel=$PREVIEW_LABEL --buildNumber=$BUILD_NUMBER +``` + +## How `samples-generate` Works + +The `CreateSamplesDirectory()` function in `scripts/cake/samples.cake`: + +1. **``** → converted to `` using the project's `` as the package ID and version from `VERSIONS.txt` +2. **Existing ``** → version updated from `VERSIONS.txt` +3. For SkiaSharp/HarfBuzzSharp packages, the preview suffix is appended +4. Two output trees: `output/samples/` (stable) and `output/samples-preview/` (preview) + +## Building with Native Binaries (for local development) + +If you need native binaries (e.g. for running samples directly with project references): + +```bash +dotnet cake --target=externals-download +``` + +> **Note:** `externals-download` clears `./output/` first. If you need both native binaries and NuGet packages, run `externals-download` first, then `docs-download-output`. + +## Troubleshooting + +### Stale cached packages +```bash +rm -rf externals/package_cache/skiasharp* externals/package_cache/harfbuzzsharp* +dotnet nuget locals all --clear +``` + +### tvOS/macOS/Tizen not building +Some platforms are disabled by default: +```bash +-p:IsNetTVOSSupported=true +-p:IsNetTizenSupported=true +-p:IsNetMacOSSupported=true +``` + +### WinUI XAML compiler failures on .NET 10 +May need a newer `Microsoft.WindowsAppSDK` version. + +### NuGet feed authentication +The SkiaSharp-CI feed is public — no authentication required. diff --git a/source/SkiaSharp.Views/SkiaSharp.Views.WindowsForms/SKControl.cs b/source/SkiaSharp.Views/SkiaSharp.Views.WindowsForms/SKControl.cs index d3653ad1c30..80b610a6d58 100644 --- a/source/SkiaSharp.Views/SkiaSharp.Views.WindowsForms/SKControl.cs +++ b/source/SkiaSharp.Views/SkiaSharp.Views.WindowsForms/SKControl.cs @@ -10,16 +10,12 @@ namespace SkiaSharp.Views.Desktop [DefaultProperty("Name")] public class SKControl : Control { - private readonly bool designMode; - private Bitmap bitmap; public SKControl() { DoubleBuffered = true; SetStyle(ControlStyles.ResizeRedraw, true); - - designMode = DesignMode || LicenseManager.UsageMode == LicenseUsageMode.Designtime; } public SKSize CanvasSize => bitmap == null ? SKSize.Empty : new SKSize(bitmap.Width, bitmap.Height); @@ -29,7 +25,7 @@ public SKControl() protected override void OnPaint(PaintEventArgs e) { - if (designMode) + if (DesignMode) return; base.OnPaint(e); diff --git a/source/SkiaSharp.Views/SkiaSharp.Views.WindowsForms/SKGLControl.cs b/source/SkiaSharp.Views/SkiaSharp.Views.WindowsForms/SKGLControl.cs index 45d8fe1f333..b2571b63c90 100644 --- a/source/SkiaSharp.Views/SkiaSharp.Views.WindowsForms/SKGLControl.cs +++ b/source/SkiaSharp.Views/SkiaSharp.Views.WindowsForms/SKGLControl.cs @@ -17,8 +17,6 @@ public class SKGLControl : GLControl private const SKColorType colorType = SKColorType.Rgba8888; private const GRSurfaceOrigin surfaceOrigin = GRSurfaceOrigin.BottomLeft; - private bool designMode; - private GRContext grContext; private GRGlFramebufferInfo glInfo; private GRBackendRenderTarget renderTarget; @@ -61,8 +59,6 @@ public SKGLControl(GraphicsMode mode, int major, int minor, GraphicsContextFlags private void Initialize() { - designMode = DesignMode || LicenseManager.UsageMode == LicenseUsageMode.Designtime; - ResizeRedraw = true; } @@ -75,7 +71,7 @@ private void Initialize() protected override void OnPaint(PaintEventArgs e) { - if (designMode) + if (DesignMode) { e.Graphics.Clear(BackColor); return;