diff --git a/.gitignore b/.gitignore index 248248ed6cb..d45a04118c3 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,5 @@ x/log_test/*.enc *.buf .osgrep .worktrees/ +AGENTS.md +CLAUDE.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 75ccb8556b9..f11cb5f8ddb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -40,7 +40,8 @@ ```bash git clone https://github.com/dgraph-io/dgraph.git cd ./dgraph -make install +make setup # auto-install tool dependencies (gotestsum, ack, etc.) +make install # build and install the dgraph binary ``` This will put the source code in a Git repo under `$GOPATH/src/github.com/dgraph-io/dgraph` and @@ -144,9 +145,15 @@ directory, providing control and flexibility beyond the standard Go testing fram The simplest way to run tests is via Make: ```bash -# Run all tests +# First-time setup: install tool dependencies +make setup + +# Run default tests (~30 min): unit, systest, core suites + integration2 make test +# Run every test in the repo +make test-full + # Run specific test types make test-unit # Unit tests only (no Docker) make test-integration2 # Integration2 tests via dgraphtest @@ -154,6 +161,7 @@ make test-upgrade # Upgrade tests # Use variables for more control make test TAGS=integration2 PKG=systest/vector +make test SUITE=all # All t/ runner suites make test TIMEOUT=90m # Override per-package timeout (default: 30m) ``` diff --git a/Makefile b/Makefile index e4f6cdd3207..54816437cd6 100644 --- a/Makefile +++ b/Makefile @@ -18,11 +18,20 @@ ifeq ($(GOPATH),) $(error GOPATH is not set. Please set it explicitly, e.g. export GOPATH=$$HOME/go) endif -# On non-Linux systems, use a separate directory for Linux binaries +# On non-Linux systems, use a separate directory for Linux binaries and +# a cross-compiler so that CGO is available (required for plugin support). ifeq ($(GOHOSTOS),linux) export LINUX_GOBIN ?= $(GOPATH)/bin + LINUX_CC ?= gcc else export LINUX_GOBIN ?= $(GOPATH)/linux_$(GOHOSTARCH) + ifeq ($(GOHOSTARCH),arm64) + LINUX_CC ?= aarch64-unknown-linux-gnu-gcc + else ifeq ($(GOHOSTARCH),amd64) + LINUX_CC ?= x86_64-unknown-linux-gnu-gcc + else + LINUX_CC ?= gcc + endif endif ###################### @@ -60,7 +69,7 @@ install: ## Install dgraph binary ifneq ($(GOHOSTOS),linux) @mkdir -p $(LINUX_GOBIN) @echo "Installing dgraph (linux/$(GOHOSTARCH))..." - @GOOS=linux GOARCH=$(GOHOSTARCH) $(MAKE) -C dgraph dgraph + @GOOS=linux GOARCH=$(GOHOSTARCH) CGO_ENABLED=1 CC=$(LINUX_CC) $(MAKE) -C dgraph BUILD_TAGS= EXTLDFLAGS=-fuse-ld=bfd dgraph @mv dgraph/dgraph $(LINUX_GOBIN)/dgraph @echo "Installed dgraph (linux/$(GOHOSTARCH)) to $(LINUX_GOBIN)/dgraph" endif @@ -78,8 +87,16 @@ dgraph-installed: $(MAKE) install; \ fi +.PHONY: deps +deps: ## Check test dependencies (pass AUTO_INSTALL=true to auto-install missing ones) + $(MAKE) -C t deps + +.PHONY: setup +setup: ## Install all test dependencies automatically + $(MAKE) deps AUTO_INSTALL=true + .PHONY: test -test: dgraph-installed local-image ## Run tests (see 'make help' for options) +test: dgraph-installed local-image ## Run tests (default: unit,systest,core + integration2) ifdef TAGS @echo "Running tests with tags: $(TAGS)" go test -v --tags="$(TAGS)" \ @@ -97,54 +114,75 @@ else done endif else - @echo "Running test suite: $(or $(SUITE),all)" - $(MAKE) -C t test args="--suite=$(or $(SUITE),all) $(if $(PKG),--pkg=\"$(PKG)\") $(if $(TEST),--test=\"$(TEST)\") $(if $(TIMEOUT),--timeout=$(TIMEOUT))" +ifdef SUITE + @echo "Running test suite: $(SUITE)" + $(MAKE) -C t test args="--suite=$(SUITE) $(if $(PKG),--pkg=\"$(PKG)\") $(if $(TEST),--test=\"$(TEST)\") $(if $(TIMEOUT),--timeout=$(TIMEOUT))" +else + @echo "Running test suites: unit, systest, core" + $(MAKE) -C t test args="--suite=unit,systest,core $(if $(PKG),--pkg=\"$(PKG)\") $(if $(TEST),--test=\"$(TEST)\") $(if $(TIMEOUT),--timeout=$(TIMEOUT))" + @echo "Running integration2 tests..." + go test -v --tags="integration2" \ + $(if $(TEST),--run="$(TEST)") \ + $(if $(PKG),./$(PKG)/...,./...) +endif endif -.PHONY: test-all -test-all: ## All test suites via t/ runner (i.e. 'make test SUITE=all') - @SUITE=all $(MAKE) test +.PHONY: test-suite +test-suite: ## Run t/ runner suite (default: all). Ex: make test-suite SUITE=unit + @SUITE=$(or $(SUITE),all) $(MAKE) test .PHONY: test-unit -test-unit: ## Unit tests, no Docker (i.e. 'make test SUITE=unit') +test-unit: ## Unit tests (i.e. 'make test SUITE=unit') + $(if $(filter command line,$(origin SUITE)),$(error SUITE= cannot be passed to test-unit; use 'make test SUITE=...' instead)) @SUITE=unit $(MAKE) test .PHONY: test-core test-core: ## Core tests (i.e. 'make test SUITE=core') + $(if $(filter command line,$(origin SUITE)),$(error SUITE= cannot be passed to test-core; use 'make test SUITE=...' instead)) @SUITE=core $(MAKE) test -.PHONY: test-integration -test-integration: ## Integration tests (i.e. 'make test TAGS=integration') - @TAGS=integration $(MAKE) test - .PHONY: test-integration2 test-integration2: ## Integration2 tests via dgraphtest (i.e. 'make test TAGS=integration2') + $(if $(filter command line,$(origin TAGS)),$(error TAGS= cannot be passed to test-integration2; use 'make test TAGS=...' instead)) @TAGS=integration2 $(MAKE) test .PHONY: test-upgrade test-upgrade: ## Upgrade tests (i.e. 'make test TAGS=upgrade') + $(if $(filter command line,$(origin TAGS)),$(error TAGS= cannot be passed to test-upgrade; use 'make test TAGS=...' instead)) @TAGS=upgrade $(MAKE) test .PHONY: test-systest test-systest: ## System integration tests (i.e. 'make test SUITE=systest') + $(if $(filter command line,$(origin SUITE)),$(error SUITE= cannot be passed to test-systest; use 'make test SUITE=...' instead)) @SUITE=systest $(MAKE) test .PHONY: test-vector test-vector: ## Vector search tests (i.e. 'make test SUITE=vector') + $(if $(filter command line,$(origin SUITE)),$(error SUITE= cannot be passed to test-vector; use 'make test SUITE=...' instead)) @SUITE=vector $(MAKE) test .PHONY: test-fuzz -test-fuzz: ## Fuzz tests, auto-discovers packages (i.e. 'make test FUZZ=1') +test-fuzz: ## Fuzz tests (i.e. 'make test FUZZ=1') + $(if $(filter command line,$(origin FUZZ)),$(error FUZZ= cannot be passed to test-fuzz; use 'make test FUZZ=...' instead)) @FUZZ=1 $(MAKE) test .PHONY: test-ldbc test-ldbc: ## LDBC benchmark tests (i.e. 'make test SUITE=ldbc') + $(if $(filter command line,$(origin SUITE)),$(error SUITE= cannot be passed to test-ldbc; use 'make test SUITE=...' instead)) @SUITE=ldbc $(MAKE) test .PHONY: test-load test-load: ## Heavy load tests (i.e. 'make test SUITE=load') + $(if $(filter command line,$(origin SUITE)),$(error SUITE= cannot be passed to test-load; use 'make test SUITE=...' instead)) @SUITE=load $(MAKE) test +.PHONY: test-full +test-full: ## Every test: all suites + integration2 + upgrade + fuzz + $(MAKE) test-suite + $(MAKE) test-integration2 + $(MAKE) test-upgrade + $(MAKE) test-fuzz + .PHONY: test-benchmark test-benchmark: ## Go benchmarks (i.e. 'go test -bench') go test -bench=. -benchmem $(if $(PKG),./$(PKG)/...,./...) @@ -152,7 +190,11 @@ test-benchmark: ## Go benchmarks (i.e. 'go test -bench') .PHONY: local-image local-image: ## Build local Docker image (dgraph/dgraph:local) @echo building local docker image - @GOOS=linux GOARCH=amd64 $(MAKE) dgraph +ifneq ($(GOHOSTOS),linux) + @GOOS=linux GOARCH=$(GOHOSTARCH) CGO_ENABLED=1 CC=$(LINUX_CC) $(MAKE) BUILD_TAGS= EXTLDFLAGS=-fuse-ld=bfd dgraph +else + @GOOS=linux GOARCH=$(GOHOSTARCH) $(MAKE) dgraph +endif @mkdir -p linux @mv ./dgraph/dgraph ./linux/dgraph @docker build -f contrib/Dockerfile -t dgraph/dgraph:local . @@ -161,6 +203,13 @@ local-image: ## Build local Docker image (dgraph/dgraph:local) .PHONY: image-local image-local: local-image ## Alias for local-image +.PHONY: clean +clean: ## Clean build artifacts + $(MAKE) -C dgraph clean + $(MAKE) -C compose clean + @rm -rf linux + @go clean -testcache + .PHONY: docker-image docker-image: dgraph ## Build Docker image (dgraph/dgraph:$VERSION) @mkdir -p linux @@ -201,7 +250,7 @@ help: ## Show available targets and variables awk 'BEGIN {FS = ":.*?## "}; {printf " %-20s %s\n", $$1, $$2}' @echo "" @echo "Variables that can be passed to 'test':" - @echo " SUITE Select t/ runner suite (e.g., make test SUITE=systest)" + @echo " SUITE Select t/ runner suite (default: unit,systest,core + integration2)" @echo " TAGS Go build tags - bypasses t/ runner (e.g., make test TAGS=integration2)" @echo " PKG Limit to specific package (e.g., make test PKG=systest/export)" @echo " TEST Run specific test function (e.g., make test TEST=TestGQLSchema)" @@ -216,8 +265,9 @@ help: ## Show available targets and variables @printf " Available TAGS values: " @grep -roh "//go:build [a-z0-9]*" --include="*_test.go" . 2>/dev/null | \ awk '{print $$2}' | \ - grep -E '^(integration|integration2|upgrade)$$' | \ + grep -E '^(integration2|upgrade)$$' | \ sort -u | tr '\n' ' ' && echo "" + @echo " Note: 'integration' tests require the t/ runner (use SUITE=, not TAGS=)" @echo "" @echo "Examples:" @echo " make test TAGS=integration2 PKG=systest/vector # integration2 tests for vector" diff --git a/TESTING.md b/TESTING.md index 1c930479fc6..e3e9c155830 100644 --- a/TESTING.md +++ b/TESTING.md @@ -128,8 +128,9 @@ The codebase is organized into several key packages: Before running tests, ensure you have the following installed and configured. -> **TL;DR:** On a fresh checkout, just run `make install` followed by `make test`. The build system -> automatically handles OS detection, builds the correct binaries, and validates dependencies. +> **TL;DR:** On a fresh checkout, run `make setup` to auto-install tool dependencies, then +> `make install` followed by `make test`. The build system automatically handles OS detection, +> builds the correct binaries, and validates dependencies. ### Automatic Dependency Checking @@ -137,11 +138,14 @@ The test framework includes scripts that check for required dependencies and can auto-install them: ```bash -# Check all dependencies (run from t/ directory) -cd t && make check +# Auto-install all missing tool dependencies (recommended for first-time setup) +make setup -# Auto-install missing dependencies -AUTO_INSTALL=true make check +# Check dependencies without installing (reports what's missing) +make deps + +# Same as 'make deps' but auto-installs anything missing +make deps AUTO_INSTALL=true ``` The check scripts validate: @@ -154,9 +158,8 @@ The check scripts validate: ### Required Tools -> **Note:** You don't need to install these manually. Running `AUTO_INSTALL=true make check` from -> the `t/` directory (or `AUTO_INSTALL=true make test` from the repo root) automatically installs -> missing dependencies. The commands below are listed for reference. +> **Note:** You don't need to install these manually. Running `make setup` from the repo root +> automatically installs missing dependencies. The commands below are listed for reference. #### 1. Go (1.21+) @@ -222,8 +225,8 @@ dgraph version The build system now handles most setup automatically. On both Linux and macOS: ```bash -# Install dependencies (optional - auto-installs if missing) -cd t && AUTO_INSTALL=true make check && cd .. +# Auto-install tool dependencies (gotestsum, ack, etc.) +make setup # Build dgraph binary (automatically handles Linux binary on macOS) make install @@ -304,21 +307,23 @@ If both pass, you're ready to run all test types! The simplest way to run tests: ```bash -# Run all tests (default) +# Run default tests (~30 min): unit, systest, core suites + integration2 make test +# Run every test in the repo (all suites + all tag-based tests + fuzz) +make test-full + # Common shortcuts (run 'make help' for full list) -make test-all # All test suites via t/ runner (i.e. 'make test SUITE=all') -make test-unit # Unit tests, no Docker (i.e. 'make test SUITE=unit') +make test-suite # All t/ runner suites (default), or: make test-suite SUITE=unit +make test-unit # Unit tests (i.e. 'make test SUITE=unit') make test-core # Core tests (i.e. 'make test SUITE=core') make test-systest # System integration tests (i.e. 'make test SUITE=systest') make test-vector # Vector search tests (i.e. 'make test SUITE=vector') make test-ldbc # LDBC benchmark tests (i.e. 'make test SUITE=ldbc') make test-load # Heavy load tests (i.e. 'make test SUITE=load') -make test-integration # Integration tests (i.e. 'make test TAGS=integration') make test-integration2 # Integration2 tests via dgraphtest (i.e. 'make test TAGS=integration2') make test-upgrade # Upgrade tests (i.e. 'make test TAGS=upgrade') -make test-fuzz # Fuzz tests, auto-discovers packages (i.e. 'make test FUZZ=1') +make test-fuzz # Fuzz tests (i.e. 'make test FUZZ=1') make test-benchmark # Go benchmarks (i.e. 'go test -bench') ``` @@ -339,7 +344,8 @@ For more control, pass variables to `make test`: | `FUZZ` | Enable fuzz testing | `make test FUZZ=1` | | `FUZZTIME` | Fuzz duration per package | `make test FUZZ=1 FUZZTIME=60s` | -**Precedence:** `TAGS` > `FUZZ` > `SUITE` (first match wins) +**Precedence:** `TAGS` > `FUZZ` > `SUITE` > default (first match wins). When no variable is set, +`make test` runs suites `unit,systest,core` plus `integration2`. ### Examples @@ -1288,6 +1294,8 @@ The following items from the original wishlist have been implemented: - **✅ Unified test interface:** A single `make test` entry point that accepts arguments to run any test type (unit, integration, integration2, upgrade, fuzz) with environment variables for control. + The default (`make test` with no args) runs `unit,systest,core` suites plus `integration2` for a + fast feedback loop (~30 min). Use `make test-full` to run all tests. - **✅ Example commands that "just work":** The following now work as expected: @@ -1295,7 +1303,7 @@ The following items from the original wishlist have been implemented: make test SUITE=systest make test FUZZ=1 PKG=dql make test TAGS=upgrade PKG=acl - make test TAGS=integration PKG=systest/plugin + make test-suite SUITE=systest PKG=systest/plugin ``` ### Remaining Ideas diff --git a/dgraph/Makefile b/dgraph/Makefile index 6608079782e..4a2de9bcc36 100644 --- a/dgraph/Makefile +++ b/dgraph/Makefile @@ -32,7 +32,8 @@ gitBranch = github.com/dgraph-io/dgraph/v25/x.gitBranch lastCommitSHA = github.com/dgraph-io/dgraph/v25/x.lastCommitSHA lastCommitTime = github.com/dgraph-io/dgraph/v25/x.lastCommitTime -BUILD_FLAGS ?= -ldflags '-X ${lastCommitSHA}=${BUILD} -X "${lastCommitTime}=${BUILD_DATE}" -X "${dgraphVersion}=${BUILD_VERSION}" -X "${dgraphCodename}=${BUILD_CODENAME}" -X ${gitBranch}=${BUILD_BRANCH}' +EXTLDFLAGS ?= +BUILD_FLAGS ?= -ldflags '-X ${lastCommitSHA}=${BUILD} -X "${lastCommitTime}=${BUILD_DATE}" -X "${dgraphVersion}=${BUILD_VERSION}" -X "${dgraphCodename}=${BUILD_CODENAME}" -X ${gitBranch}=${BUILD_BRANCH}$(if $(EXTLDFLAGS), -extldflags "$(EXTLDFLAGS)")' # Insert build tags if specified ifneq ($(strip $(BUILD_TAGS)),) diff --git a/dgraphtest/local_cluster.go b/dgraphtest/local_cluster.go index 504feac2b26..80a0fb10b9e 100644 --- a/dgraphtest/local_cluster.go +++ b/dgraphtest/local_cluster.go @@ -1322,11 +1322,19 @@ func (c *LocalCluster) GeneratePlugins(raceEnabled bool) error { if raceEnabled { opts = append(opts, "-race") } - opts = append(opts, "-buildmode=plugin", "-o", so, src) - os.Setenv("GOOS", "linux") - os.Setenv("GOARCH", "amd64") + opts = append(opts, "-buildmode=plugin") + if runtime.GOOS != "linux" { + // Use the BFD linker; the default gold linker is not shipped + // with most cross-compiler toolchains. + opts = append(opts, "-ldflags", "-extldflags -fuse-ld=bfd") + } + opts = append(opts, "-o", so, src) cmd := exec.Command("go", opts...) cmd.Dir = filepath.Dir(curr) + cmd.Env = append(os.Environ(), "GOOS=linux", "GOARCH="+runtime.GOARCH) + if runtime.GOOS != "linux" { + cmd.Env = append(cmd.Env, "CGO_ENABLED=1", "CC="+linuxCrossCC()) + } if out, err := cmd.CombinedOutput(); err != nil { log.Printf("Error: %v\n", err) log.Printf("Output: %v\n", string(out)) @@ -1347,6 +1355,22 @@ func (c *LocalCluster) GeneratePlugins(raceEnabled bool) error { return nil } +// linuxCrossCC returns the C cross-compiler for targeting Linux from the current host. +// Respects the LINUX_CC environment variable if set. +func linuxCrossCC() string { + if cc := os.Getenv("LINUX_CC"); cc != "" { + return cc + } + switch runtime.GOARCH { + case "arm64": + return "aarch64-unknown-linux-gnu-gcc" + case "amd64": + return "x86_64-unknown-linux-gnu-gcc" + default: + return "gcc" + } +} + func (c *LocalCluster) GetAlphaGrpcPublicPort(id int) (string, error) { return publicPort(c.dcli, c.alphas[id], alphaGrpcPort) } diff --git a/t/Makefile b/t/Makefile index 14ed4d61d68..b6e35a851f5 100644 --- a/t/Makefile +++ b/t/Makefile @@ -18,12 +18,15 @@ endif all: test -.PHONY: check -check: check-go check-docker check-gotestsum check-ack +.PHONY: deps +deps: check-go check-docker check-gotestsum check-ack check-cross-compiler @if [ "$(GOOS)" = "linux" ]; then \ which protoc > /dev/null 2>&1 || (echo "Error: protoc is not installed or not in PATH" && exit 1); \ fi @echo "All dependencies are installed" + +.PHONY: check +check: deps @echo "LINUX_GOBIN=$(LINUX_GOBIN)" @if [ -f "$(LINUX_GOBIN)/dgraph" ]; then \ file $(LINUX_GOBIN)/dgraph | grep -q "ELF.*executable" || (echo "Error: dgraph binary at $(LINUX_GOBIN)/dgraph is not a Linux executable" && exit 1); \ @@ -44,6 +47,10 @@ check-gotestsum: check-ack: @./scripts/check-ack.sh +.PHONY: check-cross-compiler +check-cross-compiler: + @./scripts/check-cross-compiler.sh + .PHONY: check-protoc check-protoc: @./scripts/check-protoc.sh diff --git a/t/scripts/check-cross-compiler.sh b/t/scripts/check-cross-compiler.sh new file mode 100755 index 00000000000..d59947ef397 --- /dev/null +++ b/t/scripts/check-cross-compiler.sh @@ -0,0 +1,106 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2310 +set -euo pipefail + +# shellcheck source=checkhelper.sh +source "$(dirname "${BASH_SOURCE[0]}")/checkhelper.sh" + +BREW_TAP="messense/macos-cross-toolchains" + +# Return the expected cross-compiler binary name for the host architecture. +get_cross_compiler() { + if [[ -n ${LINUX_CC-} ]]; then + echo "${LINUX_CC}" + return + fi + local arch + arch="$(uname -m)" + case "${arch}" in + arm64 | aarch64) + echo "aarch64-unknown-linux-gnu-gcc" + ;; + x86_64) + echo "x86_64-unknown-linux-gnu-gcc" + ;; + *) + err "unsupported architecture: ${arch}" + return 1 + ;; + esac +} + +# Return the Homebrew formula name for the cross-compiler. +get_brew_formula() { + local arch + arch="$(uname -m)" + case "${arch}" in + arm64 | aarch64) + echo "aarch64-unknown-linux-gnu" + ;; + x86_64) + echo "x86_64-unknown-linux-gnu" + ;; + *) + err "unsupported architecture: ${arch}" + return 1 + ;; + esac +} + +install_cross_compiler_macos() { + ensure_brew || exit 1 + local formula + formula="$(get_brew_formula)" + brew tap "${BREW_TAP}" + brew install "${BREW_TAP}/${formula}" +} + +main() { + # Cross-compiler is only needed on non-Linux systems + if [[ "$(get_os)" == "Linux" ]]; then + exit 0 + fi + + local cc + cc="$(get_cross_compiler)" + + if command -v "${cc}" &>/dev/null; then + exit 0 + fi + + if [[ ${AUTO_INSTALL-} == "true" ]]; then + install_cross_compiler_macos + if command -v "${cc}" &>/dev/null; then + exit 0 + else + err "cross-compiler check still failing after installation" + exit 1 + fi + fi + + echo "" + err "Linux cross-compiler is not installed (needed for Go plugin builds)" + echo "" + err "Required binary: ${cc}" + echo "" + err "Please install manually:" + + case "$(get_os)" in + Darwin) + local formula + formula="$(get_brew_formula)" + err " brew tap ${BREW_TAP}" + err " brew install ${BREW_TAP}/${formula}" + echo "" + err 'Or set LINUX_CC to a custom cross-compiler (e.g. "zig cc -target ...")' + ;; + *) + err " (install a gcc cross-compiler targeting linux for your architecture)" + ;; + esac + + print_auto_install_hint + exit 1 +} + +main "$@" diff --git a/t/t.go b/t/t.go index 37ff7fe1795..9435c103109 100644 --- a/t/t.go +++ b/t/t.go @@ -422,8 +422,19 @@ func sanitizeFilename(pkg string) string { return strings.ReplaceAll(pkg, "/", "_") } +// gotestsumBin returns the absolute path to gotestsum inside $GOPATH/bin. +// This avoids relying on $PATH, which may not include $GOPATH/bin on all machines +// (the check-gotestsum.sh script validates at this same path). +func gotestsumBin() string { + gopath := os.Getenv("GOPATH") + if gopath == "" { + gopath = filepath.Join(os.Getenv("HOME"), "go") + } + return filepath.Join(gopath, "bin", "gotestsum") +} + func runTestsFor(ctx context.Context, pkg, prefix string, xmlFile string) error { - args := []string{"gotestsum", "--junitfile", xmlFile, "--format", "standard-verbose", "--max-fails", "1", "--", + args := []string{gotestsumBin(), "--junitfile", xmlFile, "--format", "standard-verbose", "--max-fails", "1", "--", "-v", "-failfast", "-tags=integration"} switch { case *testTimeout != "": diff --git a/testutil/plugin.go b/testutil/plugin.go index 6beda3ea5a7..f3e4b0d5843 100644 --- a/testutil/plugin.go +++ b/testutil/plugin.go @@ -7,6 +7,7 @@ package testutil import ( "fmt" + "os" "os/exec" "path/filepath" "runtime" @@ -33,9 +34,19 @@ func GeneratePlugins(raceEnabled bool) { if raceEnabled { opts = append(opts, "-race") } - opts = append(opts, "-buildmode=plugin", "-o", so, src) + opts = append(opts, "-buildmode=plugin") + if runtime.GOOS != "linux" { + // Use the BFD linker; the default gold linker is not shipped + // with most cross-compiler toolchains. + opts = append(opts, "-ldflags", "-extldflags -fuse-ld=bfd") + } + opts = append(opts, "-o", so, src) cmd := exec.Command("go", opts...) cmd.Dir = filepath.Dir(curr) + cmd.Env = append(os.Environ(), "GOOS=linux", "GOARCH="+runtime.GOARCH) + if runtime.GOOS != "linux" { + cmd.Env = append(cmd.Env, "CGO_ENABLED=1", "CC="+linuxCC()) + } if out, err := cmd.CombinedOutput(); err != nil { fmt.Printf("Error: %v\n", err) fmt.Printf("Output: %v\n", string(out)) @@ -51,3 +62,19 @@ func GeneratePlugins(raceEnabled bool) { fmt.Printf("plugin build completed. Files are: %s\n", strings.Join(soFiles, ",")) } + +// linuxCC returns the C cross-compiler for targeting Linux from the current host. +// Respects the LINUX_CC environment variable if set. +func linuxCC() string { + if cc := os.Getenv("LINUX_CC"); cc != "" { + return cc + } + switch runtime.GOARCH { + case "arm64": + return "aarch64-unknown-linux-gnu-gcc" + case "amd64": + return "x86_64-unknown-linux-gnu-gcc" + default: + return "gcc" + } +}