CanvasKit Build Flags: The Partial Compilation That Wasn't

CanvasKit build flags promise modular compilation. Want a smaller bundle? Disable modules you don't need:

./compile.sh no_skottie no_font no_paragraph

Compile. Link error: undefined symbol: SkTypeface_FreeType::MakeFromStream.

The no_font flag didn't actually disable fonts. Skottie still needed them.

The Problem

CanvasKit's GN build system has flags like:

skia_canvaskit_enable_skottie = true
skia_canvaskit_enable_font = true
skia_canvaskit_enable_paragraph = true

Set enable_font = false, you'd expect: no font code compiled.

Reality: some font code compiled anyway, because other enabled modules depend on it.

The dependency graph isn't fully declared.

First Attempt: Disable Everything

Try to build minimal CanvasKit—just paths and basic rendering:

./compile.sh no_skottie no_font no_alias_font no_paragraph primitive_shaper

Build output:

Compiling PathOps... ✓
Compiling Core... ✓
Linking...
Error: undefined symbol: SkTypeface_FreeType::MakeFromStream
Error: undefined symbol: SkParagraph::layout

Wait. We disabled fonts and paragraph. Why are these symbols being referenced?

Tracing the Dependencies

Skottie (animation library) internally uses:

Even with no_font, if Skottie is enabled, it pulls in font dependencies.

But the BUILD.gn file didn't declare this:

if (skia_enable_skottie) {
  deps += ["../../modules/skottie:skottie"]
  # Missing: deps += ["freetype", "paragraph"]
}

The dependencies were implicit—they worked when everything was enabled, broke when selectively disabling.

The PathOps Case

Disabled Skottie, build still failed:

Error: undefined symbol: Simplify(SkPath const&, SkPath*)

This is PathOps—boolean path operations (union, intersection). Vector networks use it for corner radius and path operations.

PathOps was being pulled in transitively through the PDF module (which CanvasKit disables by default). When PDF was disabled, PathOps wasn't linked, but our code still called it.

Fixed by explicitly declaring the dependency (BUILD.gn:97):

if (skia_canvaskit_enable_pathops) {
  deps += [ "../..:pathops" ]
}

Now PathOps links correctly when needed.

The "Works on My Machine" Problem

These issues weren't caught because developers typically build with all flags enabled. The minimal configurations were untested.

The build system appeared to work—it compiled successfully with default settings. Only when disabling modules did the missing dependencies surface.

The Solution: Manual Dependency Auditing

No automated tool found all the issues. We had to:

  1. Try each flag combination
  2. Note the linker errors
  3. Find which module provides the missing symbol
  4. Add explicit dependency

For CanvasKit, this meant:

# What we added:
if (skia_enable_skottie) {
  deps += [
    "../../modules/skottie:skottie",
    "../../modules/skresources:skresources",
    "../../modules/sksg:sksg",
    # Note: Skottie also needs fonts, but that's controlled separately
  ]
}

if (skia_canvaskit_enable_pathops) {
  deps += [ "../..:pathops" ]  # Explicit, not transitive
}

The Minimal Working Configuration

After fixing dependencies, this combination works:

./compile.sh no_skottie no_font no_alias_font primitive_shaper

Result:

The flags do work, but only after declaring all dependencies explicitly.

Results

Build flags now produce working minimal builds:

The lesson: build systems lie. Flags that appear to work (compilation succeeds) may produce broken binaries (linking fails). Test every configuration you advertise.

Transitive dependencies are convenient until you try to disable them.


Read next: Frame Capture Performance: Pixels to Pixels - Async pixel readback without stalls.