package main
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
)
const (
linkmodeComponentLibs = "component-libs"
linkmodeDylib = "dylib"
)
type pkg struct {
llvmpath, pkgpath string
}
var packages = []pkg{
{"bindings/go/llvm", "llvm.org/llvm/bindings/go/llvm"},
}
type compilerFlags struct {
cpp, cxx, ld string
}
var components = []string{
"all-targets",
"analysis",
"asmparser",
"asmprinter",
"bitreader",
"bitwriter",
"codegen",
"core",
"coroutines",
"debuginfodwarf",
"executionengine",
"instrumentation",
"interpreter",
"ipo",
"irreader",
"linker",
"mc",
"mcjit",
"objcarcopts",
"option",
"profiledata",
"scalaropts",
"support",
"target",
}
func llvmConfig(args ...string) string {
configpath := os.Getenv("LLVM_CONFIG")
if configpath == "" {
bin, _ := filepath.Split(os.Args[0])
configpath = filepath.Join(bin, "llvm-config")
}
cmd := exec.Command(configpath, args...)
cmd.Stderr = os.Stderr
out, err := cmd.Output()
if err != nil {
panic(err.Error())
}
outstr := string(out)
outstr = strings.TrimSuffix(outstr, "\n")
outstr = strings.Replace(outstr, "\n", " ", -1)
return outstr
}
func llvmFlags() compilerFlags {
args := append([]string{"--ldflags", "--libs", "--system-libs"}, components...)
ldflags := llvmConfig(args...)
stdLibOption := ""
if strings.Contains(llvmConfig("--cxxflags"), "-stdlib=libc++") {
stdLibOption = "-stdlib=libc++"
}
if runtime.GOOS == "aix" {
ldflags = "-Wl,-blibpath:" + llvmConfig("--libdir") + " " + ldflags
} else if runtime.GOOS != "darwin" {
ldflags = "-Wl,-rpath," + llvmConfig("--libdir") + " " + ldflags
}
return compilerFlags{
cpp: llvmConfig("--cppflags"),
cxx: "-std=c++14" + " " + stdLibOption,
ld: ldflags,
}
}
func addTag(args []string, tag string) []string {
args = append([]string{}, args...)
addedTag := false
for i, a := range args {
if strings.HasPrefix(a, "-tags=") {
args[i] = a + " " + tag
addedTag = true
} else if a == "-tags" && i+1 < len(args) {
args[i+1] = args[i+1] + " " + tag
addedTag = true
}
}
if !addedTag {
args = append([]string{args[0], "-tags", tag}, args[1:]...)
}
return args
}
func printComponents() {
fmt.Println(strings.Join(components, " "))
}
func printConfig() {
flags := llvmFlags()
fmt.Printf(`// +build !byollvm
// This file is generated by llvm-go, do not edit.
package llvm
/*
#cgo CPPFLAGS: %s
#cgo CXXFLAGS: %s
#cgo LDFLAGS: %s
*/
import "C"
type (run_build_sh int)
`, flags.cpp, flags.cxx, flags.ld)
}
func runGoWithLLVMEnv(args []string, cc, cxx, gocmd, llgo, cppflags, cxxflags, ldflags string, packages []pkg) {
args = addTag(args, "byollvm")
srcdir := llvmConfig("--src-root")
tmpgopath, err := ioutil.TempDir("", "gopath")
if err != nil {
panic(err.Error())
}
for _, p := range packages {
path := filepath.Join(tmpgopath, "src", p.pkgpath)
err := os.MkdirAll(filepath.Dir(path), os.ModePerm)
if err != nil {
panic(err.Error())
}
abspath := p.llvmpath
if !filepath.IsAbs(abspath) {
abspath = filepath.Join(srcdir, abspath)
}
err = os.Symlink(abspath, path)
if err != nil {
panic(err.Error())
}
}
newpath := os.Getenv("PATH")
newgopathlist := []string{tmpgopath}
newgopathlist = append(newgopathlist, filepath.SplitList(os.Getenv("GOPATH"))...)
newgopath := strings.Join(newgopathlist, string(filepath.ListSeparator))
flags := llvmFlags()
newenv := []string{
"CC=" + cc,
"CXX=" + cxx,
"CGO_CPPFLAGS=" + flags.cpp + " " + cppflags,
"CGO_CXXFLAGS=" + flags.cxx + " " + cxxflags,
"CGO_LDFLAGS=" + flags.ld + " " + ldflags,
"GOPATH=" + newgopath,
"PATH=" + newpath,
}
if llgo != "" {
newenv = append(newenv, "GCCGO="+llgo)
}
for _, v := range os.Environ() {
if !strings.HasPrefix(v, "CC=") &&
!strings.HasPrefix(v, "CXX=") &&
!strings.HasPrefix(v, "CGO_CPPFLAGS=") &&
!strings.HasPrefix(v, "CGO_CXXFLAGS=") &&
!strings.HasPrefix(v, "CGO_LDFLAGS=") &&
!strings.HasPrefix(v, "GCCGO=") &&
!strings.HasPrefix(v, "GOPATH=") &&
!strings.HasPrefix(v, "PATH=") {
newenv = append(newenv, v)
}
}
gocmdpath, err := exec.LookPath(gocmd)
if err != nil {
panic(err.Error())
}
proc, err := os.StartProcess(gocmdpath, append([]string{gocmd}, args...),
&os.ProcAttr{
Env: newenv,
Files: []*os.File{os.Stdin, os.Stdout, os.Stderr},
})
if err != nil {
panic(err.Error())
}
ps, err := proc.Wait()
if err != nil {
panic(err.Error())
}
os.RemoveAll(tmpgopath)
if !ps.Success() {
os.Exit(1)
}
}
func usage() {
fmt.Println(`Usage: llvm-go subcommand [flags]
Available subcommands: build get install run test print-components print-config`)
os.Exit(0)
}
func main() {
cc := os.Getenv("CC")
cxx := os.Getenv("CXX")
cppflags := os.Getenv("CGO_CPPFLAGS")
cxxflags := os.Getenv("CGO_CXXFLAGS")
ldflags := os.Getenv("CGO_LDFLAGS")
gocmd := "go"
llgo := ""
packagesString := ""
flags := []struct {
name string
dest *string
}{
{"cc", &cc},
{"cxx", &cxx},
{"go", &gocmd},
{"llgo", &llgo},
{"cppflags", &cppflags},
{"ldflags", &ldflags},
{"packages", &packagesString},
}
args := os.Args[1:]
LOOP:
for {
if len(args) == 0 {
usage()
}
for _, flag := range flags {
if strings.HasPrefix(args[0], flag.name+"=") {
*flag.dest = args[0][len(flag.name)+1:]
args = args[1:]
continue LOOP
}
}
break
}
packages := packages
if packagesString != "" {
for _, field := range strings.Fields(packagesString) {
pos := strings.IndexRune(field, '=')
if pos == -1 {
fmt.Fprintf(os.Stderr, "invalid packages value %q, expected 'pkgpath=llvmpath [pkgpath=llvmpath ...]'\n", packagesString)
os.Exit(1)
}
packages = append(packages, pkg{
pkgpath: field[:pos],
llvmpath: field[pos+1:],
})
}
}
switch args[0] {
case "build", "get", "install", "run", "test":
runGoWithLLVMEnv(args, cc, cxx, gocmd, llgo, cppflags, cxxflags, ldflags, packages)
case "print-components":
printComponents()
case "print-config":
printConfig()
default:
usage()
}
}