| //===-- llvm-go.go - go tool wrapper for LLVM -----------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // |
| // This tool lets us build LLVM components within the tree by setting up a |
| // $GOPATH that resembles a tree fetched in the normal way with "go get". |
| // |
| //===----------------------------------------------------------------------===// |
| |
| 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++") { |
| // If libc++ is used to build LLVM libraries, -stdlib=libc++ is |
| // needed to resolve dependent symbols |
| stdLibOption = "-stdlib=libc++" |
| } |
| if runtime.GOOS != "darwin" { |
| // OS X doesn't like -rpath with cgo. See: |
| // https://github.com/golang/go/issues/7293 |
| 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() |
| } |
| } |