-
Notifications
You must be signed in to change notification settings - Fork 699
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Move target and force users to use one of the predefined targets. Also export logic to generate build contraints from goarches. Signed-off-by: Lorenz Bauer <[email protected]>
- Loading branch information
Showing
6 changed files
with
346 additions
and
289 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
package gen | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"go/build/constraint" | ||
"maps" | ||
"runtime" | ||
"slices" | ||
) | ||
|
||
var ErrInvalidTarget = errors.New("unsupported target") | ||
|
||
var targetsByGoArch = map[GoArch]Target{ | ||
"386": {"bpfel", "x86"}, | ||
"amd64": {"bpfel", "x86"}, | ||
"arm": {"bpfel", "arm"}, | ||
"arm64": {"bpfel", "arm64"}, | ||
"loong64": {"bpfel", "loongarch"}, | ||
"mips": {"bpfeb", "mips"}, | ||
"mipsle": {"bpfel", ""}, | ||
"mips64": {"bpfeb", ""}, | ||
"mips64le": {"bpfel", ""}, | ||
"ppc64": {"bpfeb", "powerpc"}, | ||
"ppc64le": {"bpfel", "powerpc"}, | ||
"riscv64": {"bpfel", "riscv"}, | ||
"s390x": {"bpfeb", "s390"}, | ||
} | ||
|
||
type Target struct { | ||
// Clang arch string, used to define the clang -target flag, as per | ||
// "clang -print-targets". | ||
clang string | ||
// Linux arch string, used to define __TARGET_ARCH_xzy macros used by | ||
// https://github.com/libbpf/libbpf/blob/master/src/bpf_tracing.h | ||
linux string | ||
} | ||
|
||
// TargetsByGoArch returns all supported targets. | ||
func TargetsByGoArch() map[GoArch]Target { | ||
return maps.Clone(targetsByGoArch) | ||
} | ||
|
||
// IsGeneric returns true if the target will compile to generic BPF. | ||
func (tgt *Target) IsGeneric() bool { | ||
return tgt.linux == "" | ||
} | ||
|
||
// Suffix returns a a string suitable for appending to a file name to | ||
// identify the target. | ||
func (tgt *Target) Suffix() string { | ||
// The output filename must not match any of the following patterns: | ||
// | ||
// *_GOOS | ||
// *_GOARCH | ||
// *_GOOS_GOARCH | ||
// | ||
// Otherwise it is interpreted as a build constraint by the Go toolchain. | ||
stem := tgt.clang | ||
if tgt.linux != "" { | ||
stem = fmt.Sprintf("%s_%s", tgt.linux, tgt.clang) | ||
} | ||
return stem | ||
} | ||
|
||
// ObsoleteSuffix returns an obsolete suffix for a subset of targets. | ||
// | ||
// It's used to work around an old bug and should not be used in new code. | ||
func (tgt *Target) ObsoleteSuffix() string { | ||
if tgt.linux == "" { | ||
return "" | ||
} | ||
|
||
return fmt.Sprintf("%s_%s", tgt.clang, tgt.linux) | ||
} | ||
|
||
// GoArch is a Go arch string. | ||
// | ||
// See https://go.dev/doc/install/source#environment for valid GOARCHes when | ||
// GOOS=linux. | ||
type GoArch string | ||
|
||
type GoArches []GoArch | ||
|
||
// Constraints is satisfied when GOARCH is any of the arches. | ||
func (arches GoArches) Constraint() constraint.Expr { | ||
var archConstraint constraint.Expr | ||
for _, goarch := range arches { | ||
tag := &constraint.TagExpr{Tag: string(goarch)} | ||
archConstraint = orConstraints(archConstraint, tag) | ||
} | ||
return archConstraint | ||
} | ||
|
||
// FindTarget turns a list of identifiers into targets and their respective | ||
// GoArches. | ||
// | ||
// The following are valid identifiers: | ||
// | ||
// - bpf: compile generic BPF for host endianness | ||
// - bpfel: compile generic BPF for little endian | ||
// - bpfeb: compile generic BPF for big endian | ||
// - native: compile BPF for host target | ||
// - $GOARCH: compile BPF for $GOARCH target | ||
// | ||
// Generic BPF can run on any target goarch with the correct endianness, | ||
// but doesn't have access to some arch specific tracing functionality. | ||
func FindTarget(id string) (Target, GoArches, error) { | ||
switch id { | ||
case "bpf", "bpfel", "bpfeb": | ||
var goarches []GoArch | ||
for arch, archTarget := range targetsByGoArch { | ||
if archTarget.clang == id { | ||
// Include tags for all goarches that have the same endianness. | ||
goarches = append(goarches, arch) | ||
} | ||
} | ||
slices.Sort(goarches) | ||
return Target{id, ""}, goarches, nil | ||
|
||
case "native": | ||
id = runtime.GOARCH | ||
fallthrough | ||
|
||
default: | ||
archTarget, ok := targetsByGoArch[GoArch(id)] | ||
if !ok || archTarget.linux == "" { | ||
return Target{}, nil, fmt.Errorf("%q: %w", id, ErrInvalidTarget) | ||
} | ||
|
||
var goarches []GoArch | ||
for goarch, lt := range targetsByGoArch { | ||
if lt == archTarget { | ||
// Include tags for all goarches that have the same | ||
// target. | ||
goarches = append(goarches, goarch) | ||
} | ||
} | ||
|
||
slices.Sort(goarches) | ||
return archTarget, goarches, nil | ||
} | ||
} | ||
|
||
func orConstraints(x, y constraint.Expr) constraint.Expr { | ||
if x == nil { | ||
return y | ||
} | ||
|
||
if y == nil { | ||
return x | ||
} | ||
|
||
return &constraint.OrExpr{X: x, Y: y} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
package gen | ||
|
||
import ( | ||
"errors" | ||
"os/exec" | ||
"slices" | ||
"testing" | ||
|
||
"github.com/go-quicktest/qt" | ||
) | ||
|
||
func TestCollectTargets(t *testing.T) { | ||
clangArches := make(map[string][]GoArch) | ||
linuxArchesLE := make(map[string][]GoArch) | ||
linuxArchesBE := make(map[string][]GoArch) | ||
for arch, archTarget := range targetsByGoArch { | ||
clangArches[archTarget.clang] = append(clangArches[archTarget.clang], arch) | ||
if archTarget.clang == "bpfel" { | ||
linuxArchesLE[archTarget.linux] = append(linuxArchesLE[archTarget.linux], arch) | ||
continue | ||
} | ||
linuxArchesBE[archTarget.linux] = append(linuxArchesBE[archTarget.linux], arch) | ||
} | ||
for i := range clangArches { | ||
slices.Sort(clangArches[i]) | ||
} | ||
for i := range linuxArchesLE { | ||
slices.Sort(linuxArchesLE[i]) | ||
} | ||
for i := range linuxArchesBE { | ||
slices.Sort(linuxArchesBE[i]) | ||
} | ||
|
||
nativeTarget, nativeArches, err := FindTarget("native") | ||
qt.Assert(t, qt.IsNil(err)) | ||
|
||
tests := []struct { | ||
short string | ||
target Target | ||
arches GoArches | ||
}{ | ||
{ | ||
"bpf", | ||
Target{"bpf", ""}, | ||
nil, | ||
}, | ||
{ | ||
"bpfel", | ||
Target{"bpfel", ""}, | ||
clangArches["bpfel"], | ||
}, | ||
{ | ||
"bpfeb", | ||
Target{"bpfeb", ""}, | ||
clangArches["bpfeb"], | ||
}, | ||
{ | ||
"amd64", | ||
Target{"bpfel", "x86"}, | ||
linuxArchesLE["x86"], | ||
}, | ||
{ | ||
"386", | ||
Target{"bpfel", "x86"}, | ||
linuxArchesLE["x86"], | ||
}, | ||
{ | ||
"ppc64", | ||
Target{"bpfeb", "powerpc"}, | ||
linuxArchesBE["powerpc"], | ||
}, | ||
{ | ||
"native", | ||
nativeTarget, | ||
nativeArches, | ||
}, | ||
} | ||
|
||
for _, test := range tests { | ||
t.Run(test.short, func(t *testing.T) { | ||
target, arches, err := FindTarget(test.short) | ||
qt.Assert(t, qt.IsNil(err)) | ||
qt.Assert(t, qt.Equals(target, test.target)) | ||
qt.Assert(t, qt.DeepEquals(arches, test.arches)) | ||
}) | ||
} | ||
} | ||
|
||
func TestCollectTargetsErrors(t *testing.T) { | ||
tests := []struct { | ||
name string | ||
target string | ||
}{ | ||
{"unknown", "frood"}, | ||
{"no linux target", "mipsle"}, | ||
} | ||
|
||
for _, test := range tests { | ||
t.Run(test.name, func(t *testing.T) { | ||
_, _, err := FindTarget(test.target) | ||
if err == nil { | ||
t.Fatal("Function did not return an error") | ||
} | ||
t.Log("Error message:", err) | ||
}) | ||
} | ||
} | ||
|
||
func TestGoarches(t *testing.T) { | ||
exe := goBin(t) | ||
|
||
for GoArch := range targetsByGoArch { | ||
t.Run(string(GoArch), func(t *testing.T) { | ||
goEnv := exec.Command(exe, "env") | ||
goEnv.Env = []string{"GOROOT=/", "GOOS=linux", "GOARCH=" + string(GoArch)} | ||
output, err := goEnv.CombinedOutput() | ||
qt.Assert(t, qt.IsNil(err), qt.Commentf("go output is:\n%s", string(output))) | ||
}) | ||
} | ||
} | ||
|
||
func TestClangTargets(t *testing.T) { | ||
exe := goBin(t) | ||
|
||
clangTargets := map[string]struct{}{} | ||
for _, tgt := range targetsByGoArch { | ||
clangTargets[tgt.clang] = struct{}{} | ||
} | ||
|
||
for target := range clangTargets { | ||
for _, env := range []string{"GOOS", "GOARCH"} { | ||
env += "=" + target | ||
t.Run(env, func(t *testing.T) { | ||
goEnv := exec.Command(exe, "env") | ||
goEnv.Env = []string{"GOROOT=/", env} | ||
output, err := goEnv.CombinedOutput() | ||
t.Log("go output is:", string(output)) | ||
qt.Assert(t, qt.IsNotNil(err), qt.Commentf("No clang target should be a valid build constraint")) | ||
}) | ||
} | ||
|
||
} | ||
} | ||
|
||
func goBin(t *testing.T) string { | ||
t.Helper() | ||
|
||
exe, err := exec.LookPath("go") | ||
if errors.Is(err, exec.ErrNotFound) { | ||
t.Skip("go binary is not in PATH") | ||
} | ||
qt.Assert(t, qt.IsNil(err)) | ||
|
||
return exe | ||
} |
Oops, something went wrong.