def _build_gdef(ufo, skipExportGlyphs=None):
    """Build a GDEF table statement (GlyphClassDef and LigatureCaretByPos).

    Building GlyphClassDef requires anchor propagation or user care to work as
    expected, as Glyphs.app also looks at anchors for classification:

    * Base: any glyph that has an attaching anchor (such as "top"; "_top" does
      not count) and is neither classified as Ligature nor Mark using the
      definitions below;
    * Ligature: if subCategory is "Ligature" and the glyph has at least one
      attaching anchor;
    * Mark: if category is "Mark" and subCategory is either "Nonspacing" or
      "Spacing Combining";
    * Compound: never assigned by Glyphs.app.

    See:

    * https://github.com/googlei18n/glyphsLib/issues/85
    * https://github.com/googlei18n/glyphsLib/pull/100#issuecomment-275430289
    """
    from glyphsLib import glyphdata

    bases, ligatures, marks, carets = set(), set(), set(), {}
    category_key = GLYPHLIB_PREFIX + "category"
    subCategory_key = GLYPHLIB_PREFIX + "subCategory"

    for glyph in ufo:
        # Do not generate any entries for non-export glyphs, as looking them up on
        # compilation will fail.
        if skipExportGlyphs is not None:
            if glyph.name in skipExportGlyphs:
                continue

        has_attaching_anchor = False
        for anchor in glyph.anchors:
            name = anchor.name
            if name and not name.startswith("_"):
                has_attaching_anchor = True
            if name and name.startswith("caret_") and "x" in anchor:
                carets.setdefault(glyph.name, []).append(round(anchor["x"]))

        # First check glyph.lib for category/subCategory overrides. Otherwise,
        # use global values from GlyphData.
        glyphinfo = glyphdata.get_glyph(glyph.name)
        category = glyph.lib.get(category_key) or glyphinfo.category
        subCategory = glyph.lib.get(subCategory_key) or glyphinfo.subCategory

        if subCategory == "Ligature" and has_attaching_anchor:
            ligatures.add(glyph.name)
        elif category == "Mark" and (
            subCategory == "Nonspacing" or subCategory == "Spacing Combining"
        ):
            marks.add(glyph.name)
        elif has_attaching_anchor:
            bases.add(glyph.name)

    if not any((bases, ligatures, marks, carets)):
        return None

    def fmt(g):
        return ("[%s]" % " ".join(sorted(g, key=ufo.glyphOrder.index))) if g else ""

    lines = [
        "table GDEF {",
        "  # automatic",
        "  GlyphClassDef",
        "    %s, # Base" % fmt(bases),
        "    %s, # Liga" % fmt(ligatures),
        "    %s, # Mark" % fmt(marks),
        "    ;",
    ]
    for glyph, caretPos in sorted(carets.items()):
        lines.append(
            "  LigatureCaretByPos %s %s;"
            % (glyph, " ".join(unicode(p) for p in sorted(caretPos)))
        )
    lines.append("} GDEF;")

    return "\n".join(lines)
Build a GDEF table statement (GlyphClassDef and LigatureCaretByPos).

    Building GlyphClassDef requires anchor propagation or user care to work as
    expected, as Glyphs.app also looks at anchors for classification:

    * Base: any glyph that has an attaching anchor (such as "top"; "_top" does
      not count) and is neither classified as Ligature nor Mark using the
      definitions below;
    * Ligature: if subCategory is "Ligature" and the glyph has at least one
      attaching anchor;
    * Mark: if category is "Mark" and subCategory is either "Nonspacing" or
      "Spacing Combining";
    * Compound: never assigned by Glyphs.app.

    See:

    * https://github.com/googlei18n/glyphsLib/issues/85
    * https://github.com/googlei18n/glyphsLib/pull/100#issuecomment-275430289