Ctrl K
ComunidadGuías…

pkg/make-release/compilers.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
package main

import (
	osPkg "os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"sync"

	"github.com/cockroachdb/errors"
)

// MacOSSDKPath is the path to where the MacOS SDK is located on Encore's builder systems
const MacOSSDKPath = "/sdk"

func GoBaseEnvs(os string, arch string) ([]string, error) {
	// Create a cache dir for the go build cache for this specific OS and architecture pair
	cacheDir, err := osPkg.UserCacheDir()
	if err != nil {
		return nil, errors.Wrap(err, "user cache dir")
	}

	path := filepath.Join(cacheDir, "encore-build-cache", "go", os, arch)

	err = osPkg.MkdirAll(path, 0755)
	if err != nil {
		return nil, errors.Wrap(err, "failed to make cache dir")
	}

	return append(osPkg.Environ(),
		"GOOS="+os,
		"GOARCH="+arch,
		"GOCACHE="+path,
	), nil
}

// CompileGoBinary compiles a Go binary for the given OS and architecture with GCP enabled
//
// This file was inspired by the blog post: https://lucor.dev/post/cross-compile-golang-fyne-project-using-zig/
func CompileGoBinary(outputPath string, entrypointPkg string, ldFlags []string, os string, arch string) error {
	cc, cxx, compilerEnvs, compilerLDFlags, err := compilerSettings(os, arch)
	if err != nil {
		return errors.Wrap(err, "compiler settings")
	}

	if os == "windows" {
		outputPath += ".exe"
	}

	combinedLDFlags := append(append([]string{}, compilerLDFlags...), ldFlags...)

	baseEnvs, err := GoBaseEnvs(os, arch)
	if err != nil {
		return errors.Wrap(err, "go base envs")
	}

	envs := append(baseEnvs,
		"CGO_ENABLED=1",
		"CC="+cc,
		"CXX="+cxx,
	)
	envs = append(envs, compilerEnvs...)

	// Build the go build args
	args := []string{"build",
		"-trimpath",
		"-tags", "netgo", // Always force netgo otherwise we end up with segfaults on MacOS
	}
	if len(combinedLDFlags) > 0 {
		args = append(args, "-ldflags="+strings.Join(combinedLDFlags, " "))
	}
	if os == "darwin" {
		args = append(args, "-buildmode=pie")
	}
	args = append(args,
		"-o", outputPath,
		entrypointPkg,
	)

	// Build the command
	cmd := exec.Command("go", args...)
	cmd.Env = envs

	// nosemgrep
	out, err := cmd.CombinedOutput()
	if err != nil {
		return errors.Newf("failed to compile go binary: %s", string(out))
	}

	return nil
}

// CompileRustBinary compiles a Rust binary for the given OS and architecture
//
// We're using zigbuild to perform easy cross compiling
func CompileRustBinary(artifactPath, outputPath string, cratePath string, os string, arch string, extraEnvVars ...string) error {
	if os == "windows" {
		if !strings.HasSuffix(artifactPath, ".dll") {
			outputPath += ".exe"
			artifactPath += ".exe"
		}
	}

	envs := append(extraEnvVars, osPkg.Environ()...)

	var target string
	switch os {
	case "darwin":
		switch arch {
		case "amd64":
			target = "x86_64-apple-darwin"
		case "arm64":
			target = "aarch64-apple-darwin"
		default:
			return errors.New("unsupported architecture for darwin: " + arch)
		}

		// We need to set the SDKROOT for cross compiling to MacOS
		if runtime.GOOS != "darwin" {
			envs = append(envs,
				"SDKROOT="+MacOSSDKPath,
			)
		}

	case "linux":
		switch arch {
		case "amd64":
			target = "x86_64-unknown-linux-gnu"
		case "arm64":
			target = "aarch64-unknown-linux-gnu"
		default:
			return errors.New("unsupported architecture for linux: " + arch)
		}

	case "windows":
		switch arch {
		case "amd64":
			target = "x86_64-pc-windows-gnu"
		default:
			return errors.New("unsupported architecture for windows: " + arch)
		}

	default:
		return errors.New("unsupported os: " + os)
	}

	// Create a cache dir for the go build cache for this specific OS and architecture pair
	cacheDir, err := osPkg.UserCacheDir()
	if err != nil {
		return errors.Wrap(err, "user cache dir")
	}

	path := filepath.Join(cacheDir, "encore-build-cache", "rust", os, arch)

	err = osPkg.MkdirAll(path, 0755)
	if err != nil {
		return errors.Wrap(err, "failed to make cache dir")
	}

	// Build the command
	cargoArgs := []string{"zigbuild",
		"--target", target,
		"--target-dir", path,
		"--release",
	}

	cmd := exec.Command("cargo", cargoArgs...)
	cmd.Dir = cratePath
	cmd.Env = envs

	// Cargo can't run multiple compiles at the same time for the same crate
	// so let's lock here, then unlock once the compile has finished
	cargoLock.Lock()
	defer cargoLock.Unlock()

	// nosemgrep
	out, err := cmd.CombinedOutput()
	if err != nil {
		return errors.Newf("failed to compile rust binary: %v - %s", err, string(out))
	}

	// Copy the binary to the output path
	binaryFile := filepath.Join(path, target, "release", artifactPath)
	cmd = exec.Command("cp", binaryFile, outputPath)
	// nosemgrep
	out, err = cmd.CombinedOutput()
	if err != nil {
		return errors.Newf("failed to copy rust binary: %v - %s", err, string(out))
	}

	return nil
}

// compilerSettings returns the CC and CXX settings for the given OS and architecture
func compilerSettings(os string, arch string) (cc, cxx string, envs, ldFlags []string, err error) {
	var zigTarget string
	var zigArgs string
	zigBinary := "zig" // pick it up off the path

	switch os {
	case "darwin":
		zigBinary = "/usr/local/zig-0.9.1/zig" // We need an explicit version of Zig for darwin (0.11.0 compiles, build causes runtime errors)
		ldFlags = []string{"-s", "-w"}

		switch arch {
		case "amd64":
			zigTarget = "x86_64-macos.10.12"
		case "arm64":
			zigTarget = "aarch64-macos.11.1"
		default:
			return "", "", nil, nil, errors.New("unsupported architecture for darwin: " + arch)
		}

		// We need to set some extra stuff if we're cross compiling to MacOS
		if runtime.GOOS != "darwin" {
			zigArgs = " -isysroot " + MacOSSDKPath + " -iwithsysroot /usr/include -iframeworkwithsysroot /System/Library/Frameworks"
			envs = []string{
				"CGO_LDFLAGS=--sysroot " + MacOSSDKPath + " -F/System/Library/Frameworks -L/usr/lib",
			}
		}

	case "linux":
		switch arch {
		case "amd64":
			zigTarget = "x86_64-linux-gnu" // Note: we're not targeting a specific glibc version here as we tried before with 2.35 - but for some reason we still get runtime errors not finding 2.34 or 2.33 on WSL (which had 2.35)
			zigArgs = " -static -isystem /usr/include"
		case "arm64":
			zigTarget = "aarch64-linux-gnu"
			zigArgs = " -static -isystem /usr/include"
			envs = []string{
				"PKG_CONFIG_LIBDIR=/usr/lib/aarch64-linux-gnu/pkgconfig",
			}
		default:
			return "", "", nil, nil, errors.New("unsupported architecture for linux: " + arch)
		}

	case "windows":
		switch arch {
		case "amd64":
			zigTarget = "x86_64-windows-gnu"
		default:
			return "", "", nil, nil, errors.New("unsupported architecture for windows: " + arch)
		}

		ldFlags = []string{"-H=windowsgui"}

	default:
		return "", "", nil, nil, errors.New("unsupported os: " + os)
	}

	cc = zigBinary + " cc -target " + zigTarget + zigArgs
	cxx = zigBinary + " c++ -target " + zigTarget + zigArgs
	return cc, cxx, envs, ldFlags, nil
}

var (
	cargoLock sync.Mutex
)