Repository: cilium/ebpf
Pull request #1560 (2024-09-06 16:28:59)

feat(gen): Make Identifier function configurable
The standard Identifier function used by the generator results in very unidiomatic go identifier names when used with my ebpf object. With this change, the Identifier function becomes configurable, allowing me to pass in something that produces better outputs in my context. If no function is passed in, we keep using the default, this makes the change entirely backwards compatible.
Furthermore, a test has been added to ensure the configuration is correctly passed through.

wdullaer: We use ebpf programs written in rust, which means the identifiers follow rust convention. Specifically map names are all caps and field names have underscores.

We're using this function:
```go
// Identifier turns a C style type or field name into an exportable Go equivalent.
func Identifier(s string) string {
	s = strings.TrimSpace(s)
	if s == "" {
		return s
	}

	capNext := true
	prevIsCap := false
	return strings.Map(func(v rune) rune {
		vIsCap := unicode.IsUpper(v)
		if capNext {
			if unicode.IsLower(v) {
				v = unicode.ToUpper(v)
			}
			vIsCap = true
		} else if prevIsCap && unicode.IsUpper(v) {
			v = unicode.ToLower(v)
		}
		prevIsCap = vIsCap

		if unicode.IsLetter(v) {
			capNext = false
			return v
		} else if unicode.IsNumber(v) {
			capNext = true
			return v
		} else {
			capNext = v == '_' || unicode.IsSpace(v) || v == '-' || v == '.'
			return -1
		}
	}, s)
}
```

I would be careful with changing the default here, since this might cause churn in clients.
I understand this is very much an aesthetic thing and the original bikeshed :-)

Patch:
diff --git a/cmd/bpf2go/gen/output.go b/cmd/bpf2go/gen/output.go
index a054fd2..201683e 100644
--- a/cmd/bpf2go/gen/output.go
+++ b/cmd/bpf2go/gen/output.go
@@ -90,10 +90,15 @@ type GenerateArgs struct {
 	ObjectFile string
 	// Output to write template to.
 	Output io.Writer
+	// Function which transforms the input into a valid go identifier. Uses the default behaviour if nil
+	Identifier func(string) string
 }
 
 // Generate bindings for a BPF ELF file.
 func Generate(args GenerateArgs) error {
+	if args.Identifier == nil {
+		args.Identifier = internal.Identifier
+	}
 	if !token.IsIdentifier(args.Stem) {
 		return fmt.Errorf("%q is not a valid identifier", args.Stem)
 	}
@@ -113,18 +118,18 @@ func Generate(args GenerateArgs) error {
 
 	maps := make(map[string]string)
 	for _, name := range args.Maps {
-		maps[name] = internal.Identifier(name)
+		maps[name] = args.Identifier(name)
 	}
 
 	programs := make(map[string]string)
 	for _, name := range args.Programs {
-		programs[name] = internal.Identifier(name)
+		programs[name] = args.Identifier(name)
 	}
 
 	typeNames := make(map[btf.Type]string)
 	for _, typ := range args.Types {
 		// NB: This also deduplicates types.
-		typeNames[typ] = args.Stem + internal.Identifier(typ.TypeName())
+		typeNames[typ] = args.Stem + args.Identifier(typ.TypeName())
 	}
 
 	// Ensure we don't have conflicting names and generate a sorted list of
@@ -136,7 +141,7 @@ func Generate(args GenerateArgs) error {
 
 	gf := &btf.GoFormatter{
 		Names:      typeNames,
-		Identifier: internal.Identifier,
+		Identifier: args.Identifier,
 	}
 
 	ctx := struct {


Test patch:
diff --git a/cmd/bpf2go/gen/output_test.go b/cmd/bpf2go/gen/output_test.go
index a0816c5..8d8b007 100644
--- a/cmd/bpf2go/gen/output_test.go
+++ b/cmd/bpf2go/gen/output_test.go
@@ -3,6 +3,7 @@ package gen
 import (
 	"bytes"
 	"fmt"
+	"strings"
 	"testing"
 
 	"github.com/go-quicktest/qt"
@@ -81,3 +82,18 @@ func TestPackageImport(t *testing.T) {
 var typesEqualComparer = cmp.Comparer(func(a, b btf.Type) bool {
 	return a == b
 })
+
+func TestCustomIdentifier(t *testing.T) {
+	var buf bytes.Buffer
+	args := GenerateArgs{
+		Package:    "foo",
+		Stem:       "bar",
+		ObjectFile: "frob.o",
+		Output:     &buf,
+		Programs:   []string{"do_thing"},
+		Identifier: strings.ToUpper,
+	}
+	err := Generate(args)
+	qt.Assert(t, qt.IsNil(err))
+	qt.Assert(t, qt.StringContains(buf.String(), "DO_THING"))
+}